Refactors plugin to use single package with addition of PlatformFile and FilePickerResult classes
This commit is contained in:
parent
0f25c14fd6
commit
1916a9acce
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.mr.flutter.plugin.filepicker.example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
0915ff87e81a3bfb122df4ced418a2b0
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
#import "GeneratedPluginRegistrant.h"
|
|
@ -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();
|
||||
}
|
|
@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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 |
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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.');
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 '';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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].
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue