[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 {}