[file_selector] Initial implementation (#3140)
Co-authored-by: Jason Panelli <jasonpanelli@google.com>
diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md
new file mode 100644
index 0000000..8dab88a
--- /dev/null
+++ b/packages/file_selector/file_selector/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.7.0
+
+* Initial Open Source release.
diff --git a/packages/file_selector/file_selector/LICENSE b/packages/file_selector/file_selector/LICENSE
new file mode 100644
index 0000000..2c91f14
--- /dev/null
+++ b/packages/file_selector/file_selector/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2020 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/packages/file_selector/file_selector/README.md b/packages/file_selector/file_selector/README.md
new file mode 100644
index 0000000..22ae707
--- /dev/null
+++ b/packages/file_selector/file_selector/README.md
@@ -0,0 +1,36 @@
+# file_selector
+
+[![pub package](https://img.shields.io/pub/v/file_selector.svg)](https://pub.dartlang.org/packages/file_selector)
+
+A Flutter plugin that manages files and interactions with file dialogs.
+
+## Usage
+To use this plugin, add `file_selector` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/).
+
+### Examples
+Here are small examples that show you how to use the API.
+Please also take a look at our [example][example] app.
+
+#### Open a single file
+``` dart
+final typeGroup = XTypeGroup(label: 'images', extensions: ['jpg', 'png']);
+final file = await openFile(acceptedTypeGroups: [typeGroup]);
+```
+
+#### Open multiple files at once
+``` dart
+final typeGroup = XTypeGroup(label: 'images', extensions: ['jpg', 'png']);
+final files = await openFiles(acceptedTypeGroups: [typeGroup]);
+```
+
+#### Saving a file
+```dart
+final path = await getSavePath();
+final name = "hello_file_selector.txt";
+final data = Uint8List.fromList("Hello World!".codeUnits);
+final mimeType = "text/plain";
+final file = XFile.fromData(data, name: name, mimeType: mimeType);
+await file.saveTo(path);
+```
+
+[example]:./example
diff --git a/packages/file_selector/file_selector/example/.gitignore b/packages/file_selector/file_selector/example/.gitignore
new file mode 100644
index 0000000..7abd075
--- /dev/null
+++ b/packages/file_selector/file_selector/example/.gitignore
@@ -0,0 +1,48 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Currently only web supported
+android/
+ios/
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/file_selector/file_selector/example/.metadata b/packages/file_selector/file_selector/example/.metadata
new file mode 100644
index 0000000..897381f
--- /dev/null
+++ b/packages/file_selector/file_selector/example/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 7736f3bc90270dcb0480db2ccffbf1d13c28db85
+ channel: dev
+
+project_type: app
diff --git a/packages/file_selector/file_selector/example/README.md b/packages/file_selector/file_selector/example/README.md
new file mode 100644
index 0000000..93260dc
--- /dev/null
+++ b/packages/file_selector/file_selector/example/README.md
@@ -0,0 +1,8 @@
+# file_selector_example
+
+Demonstrates how to use the file_selector plugin.
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](https://flutter.dev/).
diff --git a/packages/file_selector/file_selector/example/lib/get_directory_page.dart b/packages/file_selector/file_selector/example/lib/get_directory_page.dart
new file mode 100644
index 0000000..2afc58f
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/get_directory_page.dart
@@ -0,0 +1,65 @@
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+
+/// Screen that shows an example of getDirectoryPath
+class GetDirectoryPage extends StatelessWidget {
+ void _getDirectoryPath(BuildContext context) async {
+ final String confirmButtonText = 'Choose';
+ final String directoryPath = await getDirectoryPath(
+ confirmButtonText: confirmButtonText,
+ );
+ await showDialog(
+ context: context,
+ builder: (context) => TextDisplay(directoryPath),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Open a text file"),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Press to ask user to choose a directory'),
+ onPressed: () => _getDirectoryPath(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// Widget that displays a text file in a dialog
+class TextDisplay extends StatelessWidget {
+ /// Directory path
+ final String directoryPath;
+
+ /// Default Constructor
+ TextDisplay(this.directoryPath);
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text('Selected Directory'),
+ content: Scrollbar(
+ child: SingleChildScrollView(
+ child: Text(directoryPath),
+ ),
+ ),
+ actions: [
+ FlatButton(
+ child: const Text('Close'),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/home_page.dart b/packages/file_selector/file_selector/example/lib/home_page.dart
new file mode 100644
index 0000000..c37d901
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/home_page.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+
+/// Home Page of the application
+class HomePage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('File Selector Demo Home Page'),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Open a text file'),
+ onPressed: () => Navigator.pushNamed(context, '/open/text'),
+ ),
+ SizedBox(height: 10),
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Open an image'),
+ onPressed: () => Navigator.pushNamed(context, '/open/image'),
+ ),
+ SizedBox(height: 10),
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Open multiple images'),
+ onPressed: () => Navigator.pushNamed(context, '/open/images'),
+ ),
+ SizedBox(height: 10),
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Save a file'),
+ onPressed: () => Navigator.pushNamed(context, '/save/text'),
+ ),
+ SizedBox(height: 10),
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Open a get directory dialog'),
+ onPressed: () => Navigator.pushNamed(context, '/directory'),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/main.dart b/packages/file_selector/file_selector/example/lib/main.dart
new file mode 100644
index 0000000..bb34918
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/main.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:example/home_page.dart';
+import 'package:example/get_directory_page.dart';
+import 'package:example/open_text_page.dart';
+import 'package:example/open_image_page.dart';
+import 'package:example/open_multiple_images_page.dart';
+import 'package:example/save_text_page.dart';
+
+void main() {
+ runApp(MyApp());
+}
+
+/// MyApp is the Main Application
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'File Selector Demo',
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ visualDensity: VisualDensity.adaptivePlatformDensity,
+ ),
+ home: HomePage(),
+ routes: {
+ '/open/image': (context) => OpenImagePage(),
+ '/open/images': (context) => OpenMultipleImagesPage(),
+ '/open/text': (context) => OpenTextPage(),
+ '/save/text': (context) => SaveTextPage(),
+ '/directory': (context) => GetDirectoryPage(),
+ },
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/open_image_page.dart b/packages/file_selector/file_selector/example/lib/open_image_page.dart
new file mode 100644
index 0000000..2821635
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/open_image_page.dart
@@ -0,0 +1,75 @@
+import 'dart:io';
+import 'package:flutter/foundation.dart';
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+
+/// Screen that shows an example of openFiles
+class OpenImagePage extends StatelessWidget {
+ void _openImageFile(BuildContext context) async {
+ final XTypeGroup typeGroup = XTypeGroup(
+ label: 'images',
+ extensions: ['jpg', 'png'],
+ );
+ final List<XFile> files = await openFiles(acceptedTypeGroups: [typeGroup]);
+ final XFile file = files[0];
+ final String fileName = file.name;
+ final String filePath = file.path;
+
+ await showDialog(
+ context: context,
+ builder: (context) => ImageDisplay(fileName, filePath),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Open an image"),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Press to open an image file(png, jpg)'),
+ onPressed: () => _openImageFile(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// Widget that displays a text file in a dialog
+class ImageDisplay extends StatelessWidget {
+ /// Image's name
+ final String fileName;
+
+ /// Image's path
+ final String filePath;
+
+ /// Default Constructor
+ ImageDisplay(this.fileName, this.filePath);
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text(fileName),
+ // On web the filePath is a blob url
+ // while on other platforms it is a system path.
+ content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)),
+ actions: [
+ FlatButton(
+ child: const Text('Close'),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart
new file mode 100644
index 0000000..7a27a0c
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart
@@ -0,0 +1,86 @@
+import 'dart:io';
+import 'package:flutter/foundation.dart';
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+
+/// Screen that shows an example of openFiles
+class OpenMultipleImagesPage extends StatelessWidget {
+ void _openImageFile(BuildContext context) async {
+ final XTypeGroup jpgsTypeGroup = XTypeGroup(
+ label: 'JPEGs',
+ extensions: ['jpg', 'jpeg'],
+ );
+ final XTypeGroup pngTypeGroup = XTypeGroup(
+ label: 'PNGs',
+ extensions: ['png'],
+ );
+ final List<XFile> files = await openFiles(acceptedTypeGroups: [
+ jpgsTypeGroup,
+ pngTypeGroup,
+ ]);
+ await showDialog(
+ context: context,
+ builder: (context) => MultipleImagesDisplay(files),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Open multiple images"),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Press to open multiple images (png, jpg)'),
+ onPressed: () => _openImageFile(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// Widget that displays a text file in a dialog
+class MultipleImagesDisplay extends StatelessWidget {
+ /// The files containing the images
+ final List<XFile> files;
+
+ /// Default Constructor
+ MultipleImagesDisplay(this.files);
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text('Gallery'),
+ // On web the filePath is a blob url
+ // while on other platforms it is a system path.
+ content: Center(
+ child: Row(
+ children: <Widget>[
+ ...files.map(
+ (file) => Flexible(
+ child: kIsWeb
+ ? Image.network(file.path)
+ : Image.file(File(file.path))),
+ )
+ ],
+ ),
+ ),
+ actions: [
+ FlatButton(
+ child: const Text('Close'),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/open_text_page.dart b/packages/file_selector/file_selector/example/lib/open_text_page.dart
new file mode 100644
index 0000000..4cb3064
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/open_text_page.dart
@@ -0,0 +1,72 @@
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+
+/// Screen that shows an example of openFile
+class OpenTextPage extends StatelessWidget {
+ void _openTextFile(BuildContext context) async {
+ final XTypeGroup typeGroup = XTypeGroup(
+ label: 'text',
+ extensions: ['txt', 'json'],
+ );
+ final XFile file = await openFile(acceptedTypeGroups: [typeGroup]);
+ final String fileName = file.name;
+ final String fileContent = await file.readAsString();
+
+ await showDialog(
+ context: context,
+ builder: (context) => TextDisplay(fileName, fileContent),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Open a text file"),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Press to open a text file (json, txt)'),
+ onPressed: () => _openTextFile(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// Widget that displays a text file in a dialog
+class TextDisplay extends StatelessWidget {
+ /// File's name
+ final String fileName;
+
+ /// File to display
+ final String fileContent;
+
+ /// Default Constructor
+ TextDisplay(this.fileName, this.fileContent);
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text(fileName),
+ content: Scrollbar(
+ child: SingleChildScrollView(
+ child: Text(fileContent),
+ ),
+ ),
+ actions: [
+ FlatButton(
+ child: const Text('Close'),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/lib/save_text_page.dart b/packages/file_selector/file_selector/example/lib/save_text_page.dart
new file mode 100644
index 0000000..b70231f
--- /dev/null
+++ b/packages/file_selector/file_selector/example/lib/save_text_page.dart
@@ -0,0 +1,65 @@
+import 'dart:typed_data';
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+
+/// Page for showing an example of saving with file_selector
+class SaveTextPage extends StatelessWidget {
+ final TextEditingController _nameController = TextEditingController();
+ final TextEditingController _contentController = TextEditingController();
+
+ void _saveFile() async {
+ final String path = await getSavePath();
+ final String text = _contentController.text;
+ final String fileName = _nameController.text;
+ final Uint8List fileData = Uint8List.fromList(text.codeUnits);
+ final String fileMimeType = 'text/plain';
+ final XFile textFile =
+ XFile.fromData(fileData, mimeType: fileMimeType, name: fileName);
+ await textFile.saveTo(path);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Save text into a file"),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Container(
+ width: 300,
+ child: TextField(
+ minLines: 1,
+ maxLines: 12,
+ controller: _nameController,
+ decoration: InputDecoration(
+ hintText: '(Optional) Suggest File Name',
+ ),
+ ),
+ ),
+ Container(
+ width: 300,
+ child: TextField(
+ minLines: 1,
+ maxLines: 12,
+ controller: _contentController,
+ decoration: InputDecoration(
+ hintText: 'Enter File Contents',
+ ),
+ ),
+ ),
+ SizedBox(height: 10),
+ RaisedButton(
+ color: Colors.blue,
+ textColor: Colors.white,
+ child: Text('Press to save a text file'),
+ onPressed: () => _saveFile(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml
new file mode 100644
index 0000000..58f0abb
--- /dev/null
+++ b/packages/file_selector/file_selector/example/pubspec.yaml
@@ -0,0 +1,78 @@
+name: example
+description: A new Flutter project.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 1.0.0+1
+
+environment:
+ sdk: ">=2.7.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ file_selector:
+ path: ../
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^0.1.3
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware.
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/packages/file_selector/file_selector/example/web/favicon.png b/packages/file_selector/file_selector/example/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/file_selector/file_selector/example/web/favicon.png
Binary files differ
diff --git a/packages/file_selector/file_selector/example/web/icons/Icon-192.png b/packages/file_selector/file_selector/example/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/file_selector/file_selector/example/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/file_selector/file_selector/example/web/icons/Icon-512.png b/packages/file_selector/file_selector/example/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/file_selector/file_selector/example/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/file_selector/file_selector/example/web/index.html b/packages/file_selector/file_selector/example/web/index.html
new file mode 100644
index 0000000..9b7a438
--- /dev/null
+++ b/packages/file_selector/file_selector/example/web/index.html
@@ -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>
diff --git a/packages/file_selector/file_selector/example/web/manifest.json b/packages/file_selector/file_selector/example/web/manifest.json
new file mode 100644
index 0000000..8c01291
--- /dev/null
+++ b/packages/file_selector/file_selector/example/web/manifest.json
@@ -0,0 +1,23 @@
+{
+ "name": "example",
+ "short_name": "example",
+ "start_url": ".",
+ "display": "standalone",
+ "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"
+ }
+ ]
+}
diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart
new file mode 100644
index 0000000..080eac4
--- /dev/null
+++ b/packages/file_selector/file_selector/lib/file_selector.dart
@@ -0,0 +1,57 @@
+// Copyright 2020 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
+
+export 'package:file_selector_platform_interface/file_selector_platform_interface.dart'
+ show XFile, XTypeGroup;
+
+/// Open file dialog for loading files and return a file path
+Future<XFile> openFile({
+ List<XTypeGroup> acceptedTypeGroups,
+ String initialDirectory,
+ String confirmButtonText,
+}) {
+ return FileSelectorPlatform.instance.openFile(
+ acceptedTypeGroups: acceptedTypeGroups,
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText);
+}
+
+/// Open file dialog for loading files and return a list of file paths
+Future<List<XFile>> openFiles({
+ List<XTypeGroup> acceptedTypeGroups,
+ String initialDirectory,
+ String confirmButtonText,
+}) {
+ return FileSelectorPlatform.instance.openFiles(
+ acceptedTypeGroups: acceptedTypeGroups,
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText);
+}
+
+/// Saves File to user's file system
+Future<String> getSavePath({
+ List<XTypeGroup> acceptedTypeGroups,
+ String initialDirectory,
+ String suggestedName,
+ String confirmButtonText,
+}) async {
+ return FileSelectorPlatform.instance.getSavePath(
+ acceptedTypeGroups: acceptedTypeGroups,
+ initialDirectory: initialDirectory,
+ suggestedName: suggestedName,
+ confirmButtonText: confirmButtonText);
+}
+
+/// Gets a directory path from a user's file system
+Future<String> getDirectoryPath({
+ String initialDirectory,
+ String confirmButtonText,
+}) async {
+ return FileSelectorPlatform.instance.getDirectoryPath(
+ initialDirectory: initialDirectory, confirmButtonText: confirmButtonText);
+}
diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml
new file mode 100644
index 0000000..5a90f04
--- /dev/null
+++ b/packages/file_selector/file_selector/pubspec.yaml
@@ -0,0 +1,21 @@
+name: file_selector
+description: Flutter plugin for opening and saving files.
+homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector
+version: 0.7.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ file_selector_platform_interface: ^1.0.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ test: ^1.3.0
+ mockito: ^4.1.1
+ plugin_platform_interface: ^1.0.0
+ pedantic: ^1.8.0
+
+environment:
+ sdk: ">=2.1.0 <3.0.0"
+ flutter: ">=1.12.13+hotfix.5 <2.0.0"
diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart
new file mode 100644
index 0000000..15756cc
--- /dev/null
+++ b/packages/file_selector/file_selector/test/file_selector_test.dart
@@ -0,0 +1,246 @@
+// Copyright 2020 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+import 'package:file_selector/file_selector.dart';
+import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
+
+void main() {
+ MockFileSelector mock;
+ final initialDirectory = '/home/flutteruser';
+ final confirmButtonText = 'Use this profile picture';
+ final suggestedName = 'suggested_name';
+ final acceptedTypeGroups = [
+ XTypeGroup(label: 'documents', mimeTypes: [
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessing',
+ ]),
+ XTypeGroup(label: 'images', extensions: [
+ 'jpg',
+ 'png',
+ ]),
+ ];
+
+ setUp(() {
+ mock = MockFileSelector();
+ FileSelectorPlatform.instance = mock;
+ });
+
+ group('openFile', () {
+ final expectedFile = XFile('path');
+
+ test('works', () async {
+ when(mock.openFile(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ )).thenAnswer((_) => Future.value(expectedFile));
+
+ final file = await openFile(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ );
+
+ expect(file, expectedFile);
+ });
+
+ test('works with no arguments', () async {
+ when(mock.openFile()).thenAnswer((_) => Future.value(expectedFile));
+
+ final file = await openFile();
+
+ expect(file, expectedFile);
+ });
+
+ test('sets the initial directory', () async {
+ when(mock.openFile(initialDirectory: initialDirectory))
+ .thenAnswer((_) => Future.value(expectedFile));
+
+ final file = await openFile(initialDirectory: initialDirectory);
+ expect(file, expectedFile);
+ });
+
+ test('sets the button confirmation label', () async {
+ when(mock.openFile(confirmButtonText: confirmButtonText))
+ .thenAnswer((_) => Future.value(expectedFile));
+
+ final file = await openFile(confirmButtonText: confirmButtonText);
+ expect(file, expectedFile);
+ });
+
+ test('sets the accepted type groups', () async {
+ when(mock.openFile(acceptedTypeGroups: acceptedTypeGroups))
+ .thenAnswer((_) => Future.value(expectedFile));
+
+ final file = await openFile(acceptedTypeGroups: acceptedTypeGroups);
+ expect(file, expectedFile);
+ });
+ });
+
+ group('openFiles', () {
+ final expectedFiles = [XFile('path')];
+
+ test('works', () async {
+ when(mock.openFiles(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ )).thenAnswer((_) => Future.value(expectedFiles));
+
+ final file = await openFiles(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ );
+
+ expect(file, expectedFiles);
+ });
+
+ test('works with no arguments', () async {
+ when(mock.openFiles()).thenAnswer((_) => Future.value(expectedFiles));
+
+ final files = await openFiles();
+
+ expect(files, expectedFiles);
+ });
+
+ test('sets the initial directory', () async {
+ when(mock.openFiles(initialDirectory: initialDirectory))
+ .thenAnswer((_) => Future.value(expectedFiles));
+
+ final files = await openFiles(initialDirectory: initialDirectory);
+ expect(files, expectedFiles);
+ });
+
+ test('sets the button confirmation label', () async {
+ when(mock.openFiles(confirmButtonText: confirmButtonText))
+ .thenAnswer((_) => Future.value(expectedFiles));
+
+ final files = await openFiles(confirmButtonText: confirmButtonText);
+ expect(files, expectedFiles);
+ });
+
+ test('sets the accepted type groups', () async {
+ when(mock.openFiles(acceptedTypeGroups: acceptedTypeGroups))
+ .thenAnswer((_) => Future.value(expectedFiles));
+
+ final files = await openFiles(acceptedTypeGroups: acceptedTypeGroups);
+ expect(files, expectedFiles);
+ });
+ });
+
+ group('getSavePath', () {
+ final expectedSavePath = '/example/path';
+
+ test('works', () async {
+ when(mock.getSavePath(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ suggestedName: suggestedName,
+ )).thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath = await getSavePath(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ acceptedTypeGroups: acceptedTypeGroups,
+ suggestedName: suggestedName,
+ );
+
+ expect(savePath, expectedSavePath);
+ });
+
+ test('works with no arguments', () async {
+ when(mock.getSavePath())
+ .thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath = await getSavePath();
+ expect(savePath, expectedSavePath);
+ });
+
+ test('sets the initial directory', () async {
+ when(mock.getSavePath(initialDirectory: initialDirectory))
+ .thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath = await getSavePath(initialDirectory: initialDirectory);
+ expect(savePath, expectedSavePath);
+ });
+
+ test('sets the button confirmation label', () async {
+ when(mock.getSavePath(confirmButtonText: confirmButtonText))
+ .thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath = await getSavePath(confirmButtonText: confirmButtonText);
+ expect(savePath, expectedSavePath);
+ });
+
+ test('sets the accepted type groups', () async {
+ when(mock.getSavePath(acceptedTypeGroups: acceptedTypeGroups))
+ .thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath =
+ await getSavePath(acceptedTypeGroups: acceptedTypeGroups);
+ expect(savePath, expectedSavePath);
+ });
+
+ test('sets the suggested name', () async {
+ when(mock.getSavePath(suggestedName: suggestedName))
+ .thenAnswer((_) => Future.value(expectedSavePath));
+
+ final savePath = await getSavePath(suggestedName: suggestedName);
+ expect(savePath, expectedSavePath);
+ });
+ });
+
+ group('getDirectoryPath', () {
+ final expectedDirectoryPath = '/example/path';
+
+ test('works', () async {
+ when(mock.getDirectoryPath(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ )).thenAnswer((_) => Future.value(expectedDirectoryPath));
+
+ final directoryPath = await getDirectoryPath(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ );
+
+ expect(directoryPath, expectedDirectoryPath);
+ });
+
+ test('works with no arguments', () async {
+ when(mock.getDirectoryPath())
+ .thenAnswer((_) => Future.value(expectedDirectoryPath));
+
+ final directoryPath = await getDirectoryPath();
+ expect(directoryPath, expectedDirectoryPath);
+ });
+
+ test('sets the initial directory', () async {
+ when(mock.getDirectoryPath(initialDirectory: initialDirectory))
+ .thenAnswer((_) => Future.value(expectedDirectoryPath));
+
+ final directoryPath =
+ await getDirectoryPath(initialDirectory: initialDirectory);
+ expect(directoryPath, expectedDirectoryPath);
+ });
+
+ test('sets the button confirmation label', () async {
+ when(mock.getDirectoryPath(confirmButtonText: confirmButtonText))
+ .thenAnswer((_) => Future.value(expectedDirectoryPath));
+
+ final directoryPath =
+ await getDirectoryPath(confirmButtonText: confirmButtonText);
+ expect(directoryPath, expectedDirectoryPath);
+ });
+ });
+}
+
+class MockFileSelector extends Mock
+ with MockPlatformInterfaceMixin
+ implements FileSelectorPlatform {}