Refactors plugin to use single package with addition of PlatformFile and FilePickerResult classes

This commit is contained in:
Miguel Ruivo 2020-09-07 20:06:18 +01:00
parent 0f25c14fd6
commit 1916a9acce
36 changed files with 595 additions and 252 deletions

View File

@ -18,6 +18,7 @@ import androidx.core.app.ActivityCompat;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
@ -100,11 +101,9 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
Log.i(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
currentItem++;
}
if (paths.size() > 1) {
finishWithSuccess(paths);
} else {
finishWithSuccess(paths.get(0));
}
finishWithSuccess(paths);
} else if (data.getData() != null) {
Uri uri = data.getData();
String fullPath;
@ -125,7 +124,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
if (fullPath != null) {
Log.i(FilePickerDelegate.TAG, "Absolute file path:" + fullPath);
finishWithSuccess(fullPath);
finishWithSuccess(Arrays.asList(fullPath));
} else {
finishWithError("unknown_path", "Failed to retrieve path.");
}

View File

@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -22,7 +22,7 @@ android {
}
defaultConfig {
applicationId "com.mr.flutter.plugin.filepickerexample"
applicationId "com.mr.flutter.plugin.filepicker.example"
minSdkVersion 16
targetSdkVersion 29
versionCode 1

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepicker.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepickerexample"
package="com.mr.flutter.plugin.filepicker.example"
xmlns:tools="http://schemas.android.com/tools">
<!-- The INTERNET permission is required for development. Specifically,

View File

@ -1,4 +1,4 @@
package com.mr.flutter.plugin.filepickerexample;
package com.mr.flutter.plugin.filepicker.example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;

View File

@ -0,0 +1,6 @@
package com.mr.flutter.plugin.filepicker.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepicker.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1 @@
0915ff87e81a3bfb122df4ced418a2b0

View File

@ -310,7 +310,6 @@
/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -366,7 +365,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,16 @@
//
// Generated file. Do not edit.
//
// ignore: unused_import
import 'dart:ui';
import 'package:file_picker/src/file_picker_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(PluginRegistry registry) {
FilePickerWeb.registerWith(registry.registrarFor(FilePickerWeb));
registry.registerMessageHandler();
}

View File

@ -5,19 +5,18 @@ import 'package:file_picker/file_picker.dart';
class FilePickerDemo extends StatefulWidget {
@override
_FilePickerDemoState createState() => new _FilePickerDemoState();
_FilePickerDemoState createState() => _FilePickerDemoState();
}
class _FilePickerDemoState extends State<FilePickerDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String _fileName;
String _path;
Map<String, String> _paths;
List<PlatformFile> _paths;
String _extension;
bool _loadingPath = false;
bool _multiPick = false;
FileType _pickingType = FileType.any;
TextEditingController _controller = new TextEditingController();
TextEditingController _controller = TextEditingController();
@override
void initState() {
@ -28,97 +27,84 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
void _openFileExplorer() async {
setState(() => _loadingPath = true);
try {
if (_multiPick) {
_path = null;
_paths = await FilePicker.getMultiFilePath(
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,
);
}
_paths = (await FilePicker.instance.pickFiles(
type: _pickingType,
allowMultiple: _multiPick,
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null,
))
?.files;
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
} catch (ex) {
print(ex);
}
if (!mounted) return;
setState(() {
_loadingPath = false;
_fileName = _path != null
? _path.split('/').last
: _paths != null ? _paths.keys.toString() : '...';
_fileName = _paths != null ? _paths.map((e) => e.name).toString() : '...';
});
}
void _clearCachedFiles() {
FilePicker.clearTemporaryFiles().then((result) {
FilePicker.instance.clearTemporaryFiles().then((result) {
_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')),
),
);
});
}
void _selectFolder() {
FilePicker.getDirectoryPath().then((value) {
setState(() => _path = value);
FilePicker.instance.getDirectoryPath().then((value) {
setState(() => _paths = [value]);
});
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
appBar: AppBar(
title: const Text('File Picker example app'),
),
body: new Center(
child: new Padding(
body: Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: new SingleChildScrollView(
child: new Column(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Padding(
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: new DropdownButton(
hint: new Text('LOAD PATH FROM'),
child: DropdownButton(
hint: Text('LOAD PATH FROM'),
value: _pickingType,
items: <DropdownMenuItem>[
new DropdownMenuItem(
child: new Text('FROM AUDIO'),
DropdownMenuItem(
child: Text('FROM AUDIO'),
value: FileType.audio,
),
new DropdownMenuItem(
child: new Text('FROM IMAGE'),
DropdownMenuItem(
child: Text('FROM IMAGE'),
value: FileType.image,
),
new DropdownMenuItem(
child: new Text('FROM VIDEO'),
DropdownMenuItem(
child: Text('FROM VIDEO'),
value: FileType.video,
),
new DropdownMenuItem(
child: new Text('FROM MEDIA'),
DropdownMenuItem(
child: Text('FROM MEDIA'),
value: FileType.media,
),
new DropdownMenuItem(
child: new Text('FROM ANY'),
DropdownMenuItem(
child: Text('FROM ANY'),
value: FileType.any,
),
new DropdownMenuItem(
child: new Text('CUSTOM FORMAT'),
DropdownMenuItem(
child: Text('CUSTOM FORMAT'),
value: FileType.custom,
),
],
@ -129,87 +115,76 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
}
})),
),
new ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 100.0),
ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: 100.0),
child: _pickingType == FileType.custom
? new TextFormField(
? TextFormField(
maxLength: 15,
autovalidate: true,
controller: _controller,
decoration:
InputDecoration(labelText: 'File extension'),
decoration: InputDecoration(labelText: 'File extension'),
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none,
)
: new Container(),
: const SizedBox(),
),
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),
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),
value: _multiPick,
),
),
new Padding(
Padding(
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
child: Column(
children: <Widget>[
new RaisedButton(
RaisedButton(
onPressed: () => _openFileExplorer(),
child: new Text("Open file picker"),
child: Text("Open file picker"),
),
new RaisedButton(
RaisedButton(
onPressed: () => _selectFolder(),
child: new Text("Pick folder"),
child: Text("Pick folder"),
),
new RaisedButton(
RaisedButton(
onPressed: () => _clearCachedFiles(),
child: new Text("Clear temporary files"),
child: Text("Clear temporary files"),
),
],
),
),
new Builder(
Builder(
builder: (BuildContext context) => _loadingPath
? Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: const CircularProgressIndicator())
: _path != null || _paths != null
? new Container(
child: const CircularProgressIndicator(),
)
: _paths != null
? 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,
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.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.map((e) => e.name).toList()[index] : _fileName ?? '...');
final path = _paths.map((e) => e.path).toList()[index].toString();
return new ListTile(
title: new Text(
return ListTile(
title: Text(
name,
),
subtitle: new Text(path),
subtitle: Text(path),
);
},
separatorBuilder:
(BuildContext context, int index) =>
new Divider(),
separatorBuilder: (BuildContext context, int index) => const Divider(),
)),
)
: new Container(),
: const SizedBox(),
),
],
),

View File

@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="shortcut icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

View File

@ -0,0 +1,23 @@
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "minimal-ui",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -253,7 +253,7 @@
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url{
[self.documentPickerController dismissViewControllerAnimated:YES completion:nil];
NSString * path = (NSString *)[url path];
_result(path);
_result(@[path]);
_result = nil;
}
@ -267,12 +267,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
[self.documentPickerController dismissViewControllerAnimated:YES completion:nil];
NSArray * result = [FileUtils resolvePath:urls];
if([result count] > 1) {
_result(result);
} else {
_result([result objectAtIndex:0]);
}
_result(result);
_result = nil;
}
@ -323,7 +318,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
return;
}
_result([pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]);
_result(@[[pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]]);
_result = nil;
}

View File

@ -1,131 +1,5 @@
import 'dart:async';
import 'dart:io';
library file_picker;
import 'package:file_picker_platform_interface/file_picker_platform_interface.dart';
import 'package:file_picker_platform_interface/method_channel_file_picker.dart';
export 'package:file_picker_platform_interface/file_picker_platform_interface.dart'
show FileType;
final MethodChannelFilePicker _filePickerPlatform = FilePickerPlatform.instance;
class FilePicker {
FilePicker._();
/// Returns an absolute file path from the calling platform.
///
/// 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.
///
/// If you plan on picking images/videos and don't want them to be compressed automatically by OS,
/// you should set `allowCompression` to [false]. Calling this on Android won't have any effect, as
/// it already provides you the original file (or integral copy).
///
/// Defaults to [FileType.any] which will display all file types.
static Future<String> getFilePath({
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
}) async =>
await _filePickerPlatform.getFiles(
type: type,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
allowCompression: allowCompression,
);
/// Returns an iterable [Map<String,String>] where the `key` is the name of the file
/// and the `value` the path.
///
/// 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.
///
/// If you plan on picking images/videos and don't want them to be compressed automatically by OS,
/// you should set `allowCompression` to [false]. Calling this on Android won't have any effect, as
/// it already provides you the original file (or integral copy).
///
/// 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,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
}) async =>
await _filePickerPlatform.getFiles(
type: type,
allowMultiple: true,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
allowCompression: allowCompression,
);
/// 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,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
}) async {
final String filePath = await _filePickerPlatform.getFiles(
type: type,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
allowCompression: allowCompression,
);
return filePath != null ? File(filePath) : null;
}
/// Returns a [List<File>] object from the selected files paths.
///
/// 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,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
}) async {
final Map<String, String> paths = await _filePickerPlatform.getFiles(
type: type,
allowMultiple: true,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
allowCompression: allowCompression,
);
return paths != null && paths.isNotEmpty
? paths.values.map((path) => File(path)).toList()
: null;
}
/// 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.
static Future<String> getDirectoryPath() async {
return _filePickerPlatform.getDirectoryPath();
}
/// Asks the underlying platform to remove any temporary files created by this plugin.
///
/// This typically relates to cached files that are stored in the cache directory of
/// 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.
static Future<bool> clearTemporaryFiles() async {
return _filePickerPlatform.clearTemporaryFiles();
}
}
export './src/file_picker.dart';
export './src/platform_file.dart';
export './src/file_picker_result.dart';

View File

@ -0,0 +1,75 @@
import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'file_picker_io.dart';
import 'file_picker_result.dart';
enum FileType {
any,
media,
image,
video,
audio,
custom,
}
enum FilePickerStatus {
picking,
done,
}
/// The interface that implementations of file_picker must implement.
///
/// Platform implementations should extend this class rather than implement it as `file_picker`
/// does not consider newly added methods to be breaking changes. Extending this class
/// (using `extends`) ensures that the subclass will get the default implementation, while
/// platform implementations that `implements` this interface will be broken by newly added
/// [FilePicker] methods.
abstract class FilePicker extends PlatformInterface {
FilePicker() : super(token: _token);
static final Object _token = Object();
static FilePicker _instance = FilePickerIO();
static FilePicker get instance => _instance;
static set instance(FilePicker instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// 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`].).
///
/// 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.
Future<FilePickerResult> pickFiles({
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
bool allowMultiple = false,
}) async =>
throw UnimplementedError('pickFiles() has not been implemented.');
/// Asks the underlying platform to remove any temporary files created by this plugin.
///
/// This typically relates to cached files that are stored in the cache directory of
/// 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<bool> 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<PlatformFile> getDirectoryPath() async => throw UnimplementedError('getDirectoryPath() has not been implemented.');
}

View File

@ -0,0 +1,86 @@
import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:file_picker/src/platform_file.dart';
import 'package:flutter/foundation.dart';
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');
/// An implementation of [FilePicker] that uses method channels.
class FilePickerIO extends FilePicker {
static const String _tag = 'MethodChannelFilePicker';
static StreamSubscription _eventSubscription;
@override
Future<FilePickerResult> pickFiles({
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
bool allowMultiple = false,
}) =>
_getPath(type, allowMultiple, allowCompression, allowedExtensions, onFileLoading);
@override
Future<bool> clearTemporaryFiles() async => _channel.invokeMethod<bool>('clear');
@override
Future<PlatformFile> getDirectoryPath() async {
try {
String result = await _channel.invokeMethod('dir', {});
if (result != null) {
return PlatformFile(path: result, isDirectory: true);
}
} on PlatformException catch (ex) {
if (ex.code == "unknown_path") {
print(
'[$_tag] Could not resolve directory path. Maybe it\'s a protected one or unsupported (such as Downloads folder). If you are on Android, make sure that you are on SDK 21 or above.');
}
}
return null;
}
Future<FilePickerResult> _getPath(
FileType fileType,
bool allowMultipleSelection,
bool allowCompression,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
) 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.');
}
try {
_eventSubscription?.cancel();
if (onFileLoading != null) {
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(data) => onFileLoading((data as bool) ? FilePickerStatus.picking : FilePickerStatus.done),
onError: (error) => throw Exception(error),
);
}
final List<String> result = await _channel.invokeListMethod(type, {
'allowMultipleSelection': allowMultipleSelection,
'allowedExtensions': allowedExtensions,
'allowCompression': allowCompression,
});
if (result == null) {
return null;
}
return FilePickerResult(result.map((file) => PlatformFile(name: file.split('/').last, path: 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');
rethrow;
}
}
}

View File

@ -0,0 +1,26 @@
import 'package:file_picker/src/platform_file.dart';
import 'package:flutter/foundation.dart';
class FilePickerResult {
const FilePickerResult(this.files);
/// Picked files.
final List<PlatformFile> files;
/// If this pick contains only a single resource.
bool get isSinglePick => files.length == 1;
/// The length of picked files.
int get count => files.length;
/// A `List<String>` containing all paths from picked files.
///
/// This may or not be available and will typically reference cached copies of
/// original files (which can be accessed through its URI property).
///
/// Only available on IO. Throws `UnsupportedError` on Web.
List<String> get paths => files.map((file) => kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path).toList();
/// A `List<String>` containing all names from picked files with its extensions.
List<String> get names => files.map((file) => file.name).toList();
}

View File

@ -0,0 +1,85 @@
import 'dart:async';
import 'dart:convert';
import 'dart:html' as html;
import 'package:file_picker/file_picker.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'file_picker_result.dart';
import 'platform_file.dart';
class FilePickerWeb extends FilePicker {
FilePickerWeb._();
static final FilePickerWeb platform = FilePickerWeb._();
static void registerWith(Registrar registrar) {
FilePicker.instance = platform;
}
@override
Future<FilePickerResult> pickFiles({
FileType type = FileType.any,
List<String> allowedExtensions,
bool allowMultiple = false,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
}) async {
final Completer<List<PlatformFile>> filesCompleter = Completer<List<PlatformFile>>();
String accept = _fileType(type, allowedExtensions);
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.draggable = true;
uploadInput.multiple = allowMultiple;
uploadInput.accept = accept;
uploadInput.click();
uploadInput.onChange.listen((e) {
final files = uploadInput.files;
final reader = html.FileReader();
List<PlatformFile> pickedFiles = [];
reader.onLoadEnd.listen((e) {
pickedFiles.add(
PlatformFile(
name: uploadInput.value.replaceAll('\\', '/'),
bytes: Base64Decoder().convert(reader.result.toString().split(",").last),
),
);
if (pickedFiles.length >= files.length) {
filesCompleter.complete(pickedFiles);
}
});
files.forEach((element) {
reader.readAsDataUrl(element);
});
});
return FilePickerResult(await filesCompleter.future);
}
static String _fileType(FileType type, List<String> allowedExtensions) {
switch (type) {
case FileType.any:
return '';
case FileType.audio:
return 'audio/*';
case FileType.image:
return 'image/*';
case FileType.video:
return 'video/*';
case FileType.media:
return 'video/*|image/*';
case FileType.custom:
return allowedExtensions.fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next');
break;
}
return '';
}
}

View File

@ -0,0 +1,40 @@
import 'dart:typed_data';
class PlatformFile {
PlatformFile({
this.path,
this.uri,
this.name,
this.bytes,
this.isDirectory = false,
});
/// The absolute path for this file instance.
///
/// Typically whis will reflect a copy cached file and not the original source,
/// also, it's not guaranteed that this path is always available as some files
/// can be protected by OS.
///
/// Available on IO only. On Web is always `null`.
final String path;
/// The URI (Universal Resource Identifier) for this file.
///
/// This is the original file resource identifier and can be used to
/// manipulate the original file (read, write, delete).
///
/// Available on IO only. On Web is always `null`.
final String uri;
/// File name including its extension.
final String name;
/// Byte data for this file.
final Uint8List bytes;
/// Whether this file references a directory or not.
final bool isDirectory;
/// File extension for this file.
String get extension => name.split('/').last;
}

View File

@ -1,13 +1,16 @@
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.13.3
version: 2.0.0
dependencies:
flutter:
sdk: flutter
flutter_web_plugins:
sdk: flutter
flutter_plugin_android_lifecycle: ^1.0.6
file_picker_platform_interface: ^1.3.1
plugin_platform_interface: ^1.0.1
environment:
sdk: ">=2.0.0 <3.0.0"
@ -22,4 +25,5 @@ flutter:
ios:
pluginClass: FilePickerPlugin
web:
default_package: file_picker_web
pluginClass: FilePickerWeb
fileName: src/file_picker_web.dart

View File

@ -1,3 +1,7 @@
## [2.0.0] - Deprecates interface
Deprecates interface in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms.
## [1.3.1] - Rollback `allowCompression`
Removes `allowCompression` from interface as it should only be used from `file_picker` (Android & iOS).

View File

@ -1,3 +1,7 @@
# MUST READ!
The interface is deprectated in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms where an interface is integrated. This should be the one used as this package is not longer mantained.
# file_picker_platform_interface
A common platform interface for the [`file_picker`][1] plugin.
@ -6,6 +10,7 @@ This interface allows platform-specific implementations of the `file_picker`
plugin, as well as the plugin itself, to ensure they are supporting the
same interface.
# Usage
To implement a new platform-specific implementation of `file_picker`, extend

View File

@ -1,7 +1,7 @@
name: file_picker_platform_interface
description: A common platform interface for the file_picker plugin that must be used to share commom features
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker/file_picker_platform_interface
version: 1.3.1
version: 2.0.0
environment:
sdk: ">=2.1.0 <3.0.0"

View File

@ -1,3 +1,7 @@
## 2.0.0
Deprecates plugin in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms.
## 1.0.2+1
Fix custom filter String creation.

View File

@ -1,3 +1,7 @@
# MUST READ!
The web standalone plugin is deprectated in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms. This should be the one used as this package is not longer mantained.
# file_picker_web
The web implementation of [`file_picker`][1].

View File

@ -1,14 +1,14 @@
name: file_picker_web
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.2+1
version: 2.0.0
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.10.0"
dependencies:
file_picker_platform_interface: ^1.3.1
file_picker_platform_interface: ^2.0.0
flutter:
sdk: flutter
flutter_web_plugins: