From 2548d8d556c9180e8c18ca3aa44b7b21316abfb7 Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 11:02:56 -0700 Subject: [PATCH 01/13] Add file_selector_platform_interface --- .../CHANGELOG.md | 3 + .../file_selector_platform_interface/LICENSE | 27 ++++ .../README.md | 26 ++++ .../lib/file_selector_platform_interface.dart | 2 + .../method_channel_file_selector.dart | 65 +++++++++ .../file_selector_interface.dart | 74 ++++++++++ .../lib/src/types/types.dart | 3 + .../lib/src/types/x_file/base.dart | 82 +++++++++++ .../lib/src/types/x_file/html.dart | 132 ++++++++++++++++++ .../lib/src/types/x_file/io.dart | 106 ++++++++++++++ .../lib/src/types/x_file/unsupported.dart | 54 +++++++ .../lib/src/types/x_file/x_file.dart | 3 + .../src/types/x_type_group/x_type_group.dart | 45 ++++++ .../lib/src/web_helpers/web_helpers.dart | 34 +++++ .../pubspec.yaml | 25 ++++ ...file_selector_platform_interface_test.dart | 43 ++++++ 16 files changed, 724 insertions(+) create mode 100644 packages/file_selector/file_selector_platform_interface/CHANGELOG.md create mode 100644 packages/file_selector/file_selector_platform_interface/LICENSE create mode 100644 packages/file_selector/file_selector_platform_interface/README.md create mode 100644 packages/file_selector/file_selector_platform_interface/lib/file_selector_platform_interface.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/base.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/html.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/web_helpers/web_helpers.dart create mode 100644 packages/file_selector/file_selector_platform_interface/pubspec.yaml create mode 100644 packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..6073234226b2 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial release. diff --git a/packages/file_selector/file_selector_platform_interface/LICENSE b/packages/file_selector/file_selector_platform_interface/LICENSE new file mode 100644 index 000000000000..0c91662b3f2f --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2020 The Chromium 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. diff --git a/packages/file_selector/file_selector_platform_interface/README.md b/packages/file_selector/file_selector_platform_interface/README.md new file mode 100644 index 000000000000..d750461f2133 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/README.md @@ -0,0 +1,26 @@ +# file_selector_platform_interface + +A common platform interface for the `file_selector` plugin. + +This interface allows platform-specific implementations of the `file_selector` +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_selector`, extend +[`FileSelectorPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`FileSelectorPlatform` by calling +`FileSelectorPlatform.instance = MyPlatformFileSelector()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../file_selector +[2]: lib/file_selector_platform_interface.dart diff --git a/packages/file_selector/file_selector_platform_interface/lib/file_selector_platform_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/file_selector_platform_interface.dart new file mode 100644 index 000000000000..69e3064150b5 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/file_selector_platform_interface.dart @@ -0,0 +1,2 @@ +export 'src/platform_interface/file_selector_interface.dart'; +export 'src/types/types.dart'; diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart new file mode 100644 index 000000000000..8ca51b8f8b4f --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -0,0 +1,65 @@ +// Copyright 2020 The Chromium 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/services.dart'; + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; + +const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_picker'); + +/// An implementation of [FileSelectorPlatform] that uses method channels. +class MethodChannelFileSelector extends FileSelectorPlatform { + /// Load a file from user's computer and return it as an XFile + @override + Future openFile({ + List acceptedTypeGroups, + String initialDirectory, + String confirmButtonText, + }) async { + String path = await _channel.invokeMethod( + 'openFiles', + { + 'acceptedTypes': acceptedTypeGroups, + 'initialDirectory': initialDirectory, + 'multiple': false, + }, + ); + return XFile(path); + } + + /// Load multiple files from user's computer and return it as an XFile + @override + Future> openFiles({ + List acceptedTypeGroups, + String initialDirectory, + String confirmButtonText, + }) async { + final pathList = await _channel.invokeMethod>( + 'openFiles', + { + 'acceptedTypes': acceptedTypeGroups, + 'initialDirectory': initialDirectory, + 'multiple': true, + }, + ); + return pathList.map((path) => XFile(path)).toList(); + } + + /// Saves the file to user's Disk + @override + Future getSavePath({ + List acceptedTypeGroups, + String initialDirectory, + String suggestedName, + String confirmButtonText, + }) async { + return _channel.invokeMethod( + 'saveFile', + { + 'initialDirectory': initialDirectory, + 'suggestedName': suggestedName, + }, + ); + } +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart new file mode 100644 index 000000000000..675fe1ee2585 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart @@ -0,0 +1,74 @@ +// Copyright 2020 The Chromium 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'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../method_channel/method_channel_file_selector.dart'; + +/// 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 +/// [FileSelectorPlatform] methods. +abstract class FileSelectorPlatform extends PlatformInterface { + /// Constructs a FileSelectorPlatform. + FileSelectorPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FileSelectorPlatform _instance = MethodChannelFileSelector(); + + /// The default instance of [FileSelectorPlatform] to use. + /// + /// Defaults to [MethodChannelFileSelector]. + static FileSelectorPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [FileSelectorPlatform] when they register themselves. + static set instance(FileSelectorPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Open file dialog for loading files and return a file path + Future openFile({ + List acceptedTypeGroups, + String initialDirectory, + String confirmButtonText, + }) { + throw UnimplementedError('openFile() has not been implemented.'); + } + + /// Open file dialog for loading files and return a list of file paths + Future> openFiles({ + List acceptedTypeGroups, + String initialDirectory, + String confirmButtonText, + }) { + throw UnimplementedError('openFiles() has not been implemented.'); + } + + /// Open file dialog for saving files and return a file path at which to save + Future getSavePath({ + List acceptedTypeGroups, + String initialDirectory, + String suggestedName, + String confirmButtonText, + }) { + throw UnimplementedError('getSavePath() has not been implemented.'); + } + + /// Open file dialog for loading directories and return a directory path + Future getDirectoryPath({ + String initialDirectory, + String confirmButtonText, + }) { + throw UnimplementedError('getDirectoryPath() has not been implemented.'); + } +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart new file mode 100644 index 000000000000..8848c6751ba3 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart @@ -0,0 +1,3 @@ +export 'x_file/x_file.dart'; + +export 'x_type_group/x_type_group.dart'; diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/base.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/base.dart new file mode 100644 index 000000000000..7ea050ff28db --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/base.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +/// The interface for a XFile. +/// +/// A XFile is a container that wraps the path of a selected +/// file by the user and (in some platforms, like web) the bytes +/// with the contents of the file. +/// +/// This class is a very limited subset of dart:io [File], so all +/// the methods should seem familiar. +abstract class XFileBase { + /// Construct a XFile + XFileBase(String path); + + /// Save the XFile at the indicated file path. + void saveTo(String path) async { + throw UnimplementedError('saveTo has not been implemented.'); + } + + /// Get the path of the picked file. + /// + /// This should only be used as a backwards-compatibility clutch + /// for mobile apps, or cosmetic reasons only (to show the user + /// the path they've picked). + /// + /// Accessing the data contained in the picked file by its path + /// is platform-dependant (and won't work on web), so use the + /// byte getters in the XFile instance instead. + String get path { + throw UnimplementedError('.path has not been implemented.'); + } + + /// The name of the file as it was selected by the user in their device. + /// + /// Use only for cosmetic reasons, do not try to use this as a path. + String get name { + throw UnimplementedError('.name has not been implemented.'); + } + + /// For web, it may be necessary for a file to know its MIME type. + String get mimeType { + throw UnimplementedError('.mimeType has not been implemented.'); + } + + /// Get the length of the file. Returns a `Future` that completes with the length in bytes. + Future length() { + throw UnimplementedError('.length() has not been implemented.'); + } + + /// Synchronously read the entire file contents as a string using the given [Encoding]. + /// + /// By default, `encoding` is [utf8]. + /// + /// Throws Exception if the operation fails. + Future readAsString({Encoding encoding = utf8}) { + throw UnimplementedError('readAsString() has not been implemented.'); + } + + /// Synchronously read the entire file contents as a list of bytes. + /// + /// Throws Exception if the operation fails. + Future readAsBytes() { + throw UnimplementedError('readAsBytes() has not been implemented.'); + } + + /// Create a new independent [Stream] for the contents of this file. + /// + /// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0). + /// + /// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file. + /// + /// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled. + Stream openRead([int start, int end]) { + throw UnimplementedError('openRead() has not been implemented.'); + } + + /// Get the last-modified time for the XFile + Future lastModified() { + throw UnimplementedError('openRead() has not been implemented.'); + } +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/html.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/html.dart new file mode 100644 index 000000000000..fe898eb4ca62 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/html.dart @@ -0,0 +1,132 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http show readBytes; +import 'package:meta/meta.dart'; +import 'dart:html'; + +import '../../web_helpers/web_helpers.dart'; +import './base.dart'; + +/// A XFile that works on web. +/// +/// It wraps the bytes of a selected file. +class XFile extends XFileBase { + String path; + + final String mimeType; + final Uint8List _data; + final int _length; + final String name; + final DateTime _lastModified; + Element _target; + + final XFileTestOverrides _overrides; + + bool get _hasTestOverrides => _overrides != null; + + /// Construct a XFile object from its ObjectUrl. + /// + /// Optionally, this can be initialized with `bytes` and `length` + /// so no http requests are performed to retrieve files later. + /// + /// `name` needs to be passed from the outside, since we only have + /// access to it while we create the ObjectUrl. + XFile( + this.path, { + this.mimeType, + this.name, + int length, + Uint8List bytes, + DateTime lastModified, + @visibleForTesting XFileTestOverrides overrides, + }) : _data = bytes, + _length = length, + _overrides = overrides, + _lastModified = lastModified, + super(path); + + /// Construct an XFile from its data + XFile.fromData( + Uint8List bytes, { + this.mimeType, + this.name, + int length, + DateTime lastModified, + this.path, + @visibleForTesting XFileTestOverrides overrides, + }) : _data = bytes, + _length = length, + _overrides = overrides, + _lastModified = lastModified, + super(path) { + if (path == null) { + final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType); + this.path = Url.createObjectUrl(blob); + } + } + + @override + Future lastModified() async { + if (_lastModified != null) { + return Future.value(_lastModified); + } + return null; + } + + Future get _bytes async { + if (_data != null) { + return Future.value(UnmodifiableUint8ListView(_data)); + } + return http.readBytes(path); + } + + @override + Future length() async { + return _length ?? (await _bytes).length; + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return encoding.decode(await _bytes); + } + + @override + Future readAsBytes() async { + return Future.value(await _bytes); + } + + @override + Stream openRead([int start, int end]) async* { + final bytes = await _bytes; + yield bytes.sublist(start ?? 0, end ?? bytes.length); + } + + /// Saves the data of this XFile at the location indicated by path. + /// For the web implementation, the path variable is ignored. + void saveTo(String path) async { + // Create a DOM container where we can host the anchor. + _target = ensureInitialized('__x_file_dom_element'); + + // Create an tag with the appropriate download attributes and click it + // May be overridden with XFileTestOverrides + final AnchorElement element = + (_hasTestOverrides && _overrides.createAnchorElement != null) + ? _overrides.createAnchorElement(this.path, this.name) + : createAnchorElement(this.path, this.name); + + // Clear the children in our container so we can add an element to click + _target.children.clear(); + addElementToContainerAndClick(_target, element); + } +} + +/// Overrides some functions to allow testing +@visibleForTesting +class XFileTestOverrides { + /// For overriding the creation of the file input element. + Element Function(String href, String suggestedName) createAnchorElement; + + /// Default constructor for overrides + XFileTestOverrides({this.createAnchorElement}); +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart new file mode 100644 index 000000000000..bd8470a1d8ca --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart @@ -0,0 +1,106 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import './base.dart'; + +/// A XFile backed by a dart:io File. +class XFile extends XFileBase { + final File _file; + final String mimeType; + final DateTime _lastModified; + int _length; + + final Uint8List _bytes; + + /// Construct a XFile object backed by a dart:io File. + XFile( + String path, { + this.mimeType, + String name, + int length, + Uint8List bytes, + DateTime lastModified, + }) : _file = File(path), + _bytes = null, + _lastModified = lastModified, + super(path); + + /// Construct an XFile from its data + XFile.fromData( + Uint8List bytes, { + this.mimeType, + String path, + String name, + int length, + DateTime lastModified, + }) : _bytes = bytes, + _file = File(path), + _length = length, + _lastModified = lastModified, + super(path) { + if (length == null) { + _length = bytes.length; + } + } + + @override + Future lastModified() { + if (_lastModified != null) { + return Future.value(_lastModified); + } + return _file.lastModified(); + } + + @override + void saveTo(String path) async { + File fileToSave = File(path); + await fileToSave.writeAsBytes(_bytes); + await fileToSave.create(); + } + + @override + String get path { + return _file.path; + } + + @override + String get name { + return _file.path.split(Platform.pathSeparator).last; + } + + @override + Future length() { + if (_length != null) { + return Future.value(_length); + } + return _file.length(); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + if (_bytes != null) { + return Future.value(String.fromCharCodes(_bytes)); + } + return _file.readAsString(encoding: encoding); + } + + @override + Future readAsBytes() { + if (_bytes != null) { + return Future.value(_bytes); + } + return _file.readAsBytes(); + } + + @override + Stream openRead([int start, int end]) async* { + if (_bytes != null) { + final bytes = _bytes; + yield bytes.sublist(start ?? 0, end ?? bytes.length); + } + yield* _file + .openRead(start ?? 0, end) + .map((chunk) => Uint8List.fromList(chunk)); + } +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart new file mode 100644 index 000000000000..f5fe388e0899 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart @@ -0,0 +1,54 @@ +import 'dart:typed_data'; +import 'package:meta/meta.dart'; + +import './base.dart'; + +/// A XFile is a cross-platform, simplified File abstraction. +/// +/// It wraps the bytes of a selected file, and its (platform-dependant) path. +class XFile extends XFileBase { + /// Construct a XFile object from its path. + /// + /// Optionally, this can be initialized with `bytes` and `length` + /// so no http requests are performed to retrieve data later. + /// + /// `name` may be passed from the outside, for those cases where the effective + /// `path` of the file doesn't match what the user sees when selecting it + /// (like in web) + XFile( + String path, { + String mimeType, + String name, + int length, + Uint8List bytes, + DateTime lastModified, + @visibleForTesting XFileTestOverrides overrides, + }) : super(path) { + throw UnimplementedError( + 'XFile is not available in your current platform.'); + } + + /// Construct a XFile object from its data + XFile.fromData( + Uint8List bytes, { + String mimeType, + String name, + int length, + DateTime lastModified, + String path, + @visibleForTesting XFileTestOverrides overrides, + }) : super(path) { + throw UnimplementedError( + 'XFile is not available in your current platform.'); + } +} + +/// Overrides some functions of XFile for testing purposes +@visibleForTesting +class XFileTestOverrides { + /// For overriding the creation of the file input element. + dynamic Function(String href, String suggestedName) createAnchorElement; + + /// Default constructor for overrides + XFileTestOverrides({this.createAnchorElement}); +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart new file mode 100644 index 000000000000..f966a7c9a3aa --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart @@ -0,0 +1,3 @@ +export 'unsupported.dart' + if (dart.library.html) 'html.dart' + if (dart.library.io) 'io.dart'; diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart new file mode 100644 index 000000000000..ed8ce9c2f396 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// A set of allowed XTypes +class XTypeGroup { + /// Creates a new group with the given label and file extensions. + XTypeGroup({ + this.label, + this.extensions, + this.mimeTypes, + this.macUTIs, + this.webWildCards, + }) : assert( + !((extensions == null || extensions.isEmpty) && + (mimeTypes == null || mimeTypes.isEmpty) && + (macUTIs == null || macUTIs.isEmpty) && + (webWildCards == null || webWildCards.isEmpty)), + "At least one type must be provided for an XTypeGroup."); + + /// The 'name' or reference to this group of types + final String label; + + /// The extensions for this group + final List extensions; + + /// The MIME types for this group + final List mimeTypes; + + /// The UTIs for this group + final List macUTIs; + + /// The web wild cards for this group (ex: image/*, video/*) + final List webWildCards; +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/web_helpers/web_helpers.dart b/packages/file_selector/file_selector_platform_interface/lib/src/web_helpers/web_helpers.dart new file mode 100644 index 000000000000..9e40e562bc9a --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/web_helpers/web_helpers.dart @@ -0,0 +1,34 @@ +import 'dart:html'; + +/// Create anchor element with download attribute +AnchorElement createAnchorElement(String href, String suggestedName) { + final element = AnchorElement(href: href); + + if (suggestedName == null) { + element.download = 'download'; + } else { + element.download = suggestedName; + } + + return element; +} + +/// Add an element to a container and click it +void addElementToContainerAndClick(Element container, Element element) { + // Add the element and click it + // All previous elements will be removed before adding the new one + container.children.add(element); + element.click(); +} + +/// Initializes a DOM container where we can host elements. +Element ensureInitialized(String id) { + var target = querySelector('#${id}'); + if (target == null) { + final Element targetElement = Element.tag('flt-x-file')..id = id; + + querySelector('body').children.add(targetElement); + target = targetElement; + } + return target; +} diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..71aa2f06d2b6 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -0,0 +1,25 @@ +name: file_selector_platform_interface +description: A common platform interface for the file_picker plugin. +homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 0.1.0 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + http: ^0.12.0+1 + plugin_platform_interface: ^1.0.1 + mime_type: ^0.3.1 + +dev_dependencies: + test: ^1.15.0 + flutter_test: + sdk: flutter + mockito: ^4.1.1 + pedantic: ^1.8.0 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart new file mode 100644 index 000000000000..eb5bb89ad5b1 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart @@ -0,0 +1,43 @@ +// Copyright 2020 The Chromium 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:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:file_selector_platform_interface/src/method_channel/method_channel_file_selector.dart'; + +void main() { + group('$FileSelectorPlatform', () { + test('$MethodChannelFileSelector() is the default instance', () { + expect(FileSelectorPlatform.instance, + isInstanceOf()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + FileSelectorPlatform.instance = ImplementsFileSelectorPlatform(); + }, throwsA(isInstanceOf())); + }); + + test('Can be mocked with `implements`', () { + final FileSelectorPlatformMock mock = FileSelectorPlatformMock(); + FileSelectorPlatform.instance = mock; + }); + + test('Can be extended', () { + FileSelectorPlatform.instance = ExtendsFileSelectorPlatform(); + }); + }); +} + +class FileSelectorPlatformMock extends Mock + with MockPlatformInterfaceMixin + implements FileSelectorPlatform {} + +class ImplementsFileSelectorPlatform extends Mock + implements FileSelectorPlatform {} + +class ExtendsFileSelectorPlatform extends FileSelectorPlatform {} From 73c4e72d71f197cdc14e70ceeccca85a99afcdfa Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 11:10:29 -0700 Subject: [PATCH 02/13] Update pubspec.yaml --- .../file_selector_platform_interface/pubspec.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index 71aa2f06d2b6..be24867c84bc 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: file_selector_platform_interface -description: A common platform interface for the file_picker plugin. -homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface +description: A common platform interface for the file_selector plugin. +homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes version: 0.1.0 @@ -11,7 +11,6 @@ dependencies: meta: ^1.0.5 http: ^0.12.0+1 plugin_platform_interface: ^1.0.1 - mime_type: ^0.3.1 dev_dependencies: test: ^1.15.0 From a8a61921bf178235577aadfdc4e63b0ce7739907 Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 16:27:02 -0700 Subject: [PATCH 03/13] Update Method Channels, add tests for method channels and XFile --- .../method_channel_file_selector.dart | 53 +++- .../src/types/x_type_group/x_type_group.dart | 11 + .../test/assets/hello.txt | 1 + .../method_channel_file_selector_test.dart | 241 ++++++++++++++++++ .../test/x_file_html_test.dart | 38 +++ .../test/x_file_io_test.dart | 50 ++++ 6 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 packages/file_selector/file_selector_platform_interface/test/assets/hello.txt create mode 100644 packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart create mode 100644 packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart create mode 100644 packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index 8ca51b8f8b4f..41ebdc9f3181 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -5,11 +5,16 @@ import 'package:flutter/services.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:meta/meta.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_picker'); /// An implementation of [FileSelectorPlatform] that uses method channels. class MethodChannelFileSelector extends FileSelectorPlatform { + /// The MethodChannel that is being used by this implementation of the plugin. + @visibleForTesting + MethodChannel get channel => _channel; + /// Load a file from user's computer and return it as an XFile @override Future openFile({ @@ -17,15 +22,17 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String initialDirectory, String confirmButtonText, }) async { - String path = await _channel.invokeMethod( - 'openFiles', - { - 'acceptedTypes': acceptedTypeGroups, + final List path = await _channel.invokeListMethod( + 'openFile', + { + 'acceptedTypeGroups': + acceptedTypeGroups?.map((group) => group.toJSON())?.toList(), 'initialDirectory': initialDirectory, + 'confirmButtonText': confirmButtonText, 'multiple': false, }, ); - return XFile(path); + return path == null ? null : XFile(path?.first); } /// Load multiple files from user's computer and return it as an XFile @@ -35,18 +42,20 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String initialDirectory, String confirmButtonText, }) async { - final pathList = await _channel.invokeMethod>( - 'openFiles', - { - 'acceptedTypes': acceptedTypeGroups, + final List pathList = await _channel.invokeListMethod( + 'openFile', + { + 'acceptedTypeGroups': + acceptedTypeGroups?.map((group) => group.toJSON())?.toList(), 'initialDirectory': initialDirectory, + 'confirmButtonText': confirmButtonText, 'multiple': true, }, ); - return pathList.map((path) => XFile(path)).toList(); + return pathList?.map((path) => XFile(path))?.toList() ?? []; } - /// Saves the file to user's Disk + /// Gets the path from a save dialog @override Future getSavePath({ List acceptedTypeGroups, @@ -55,10 +64,28 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String confirmButtonText, }) async { return _channel.invokeMethod( - 'saveFile', - { + 'getSavePath', + { + 'acceptedTypeGroups': + acceptedTypeGroups?.map((group) => group.toJSON())?.toList(), 'initialDirectory': initialDirectory, 'suggestedName': suggestedName, + 'confirmButtonText': confirmButtonText, + }, + ); + } + + /// Gets a directory path from a dialog + @override + Future getDirectoryPath({ + String initialDirectory, + String confirmButtonText, + }) async { + return _channel.invokeMethod( + 'getDirectoryPath', + { + 'initialDirectory': initialDirectory, + 'confirmButtonText': confirmButtonText, }, ); } diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart index ed8ce9c2f396..b14a3fa235b2 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart @@ -42,4 +42,15 @@ class XTypeGroup { /// The web wild cards for this group (ex: image/*, video/*) final List webWildCards; + + /// Converts this object into a JSON formatted object + Map toJSON() { + return { + 'label': label, + 'extensions': extensions, + 'mimeTypes': mimeTypes, + 'macUTIs': macUTIs, + 'webWildCards': webWildCards, + }; + } } diff --git a/packages/file_selector/file_selector_platform_interface/test/assets/hello.txt b/packages/file_selector/file_selector_platform_interface/test/assets/hello.txt new file mode 100644 index 000000000000..5dd01c177f5d --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/assets/hello.txt @@ -0,0 +1 @@ +Hello, world! \ No newline at end of file diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart new file mode 100644 index 000000000000..57638e2d6e7b --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -0,0 +1,241 @@ +// Copyright 2019 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/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:file_selector_platform_interface/src/method_channel/method_channel_file_selector.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelFileSelector()', () { + MethodChannelFileSelector plugin = MethodChannelFileSelector(); + + final List log = []; + + setUp(() { + plugin.channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return null; + }); + + log.clear(); + }); + + group('#openFile', () { + test('passes the accepted type groups correctly', () async { + final group = XTypeGroup( + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + webWildCards: ['document/*']); + + final groupTwo = XTypeGroup( + label: 'image', + extensions: ['.jpg'], + mimeTypes: ['image/jpg'], + macUTIs: ['public.image'], + webWildCards: ['image/*']); + + await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': [group.toJSON(), groupTwo.toJSON()], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }), + ], + ); + }); + test('passes initialDirectory correctly', () async { + await plugin.openFile(initialDirectory: "/example/directory"); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': "/example/directory", + 'confirmButtonText': null, + 'multiple': false, + }), + ], + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.openFile(confirmButtonText: "Open File"); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': null, + 'confirmButtonText': "Open File", + 'multiple': false, + }), + ], + ); + }); + }); + group('#openFiles', () { + test('passes the accepted type groups correctly', () async { + final group = XTypeGroup( + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + webWildCards: ['document/*']); + + final groupTwo = XTypeGroup( + label: 'image', + extensions: ['.jpg'], + mimeTypes: ['image/jpg'], + macUTIs: ['public.image'], + webWildCards: ['image/*']); + + await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': [group.toJSON(), groupTwo.toJSON()], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': true, + }), + ], + ); + }); + test('passes initialDirectory correctly', () async { + await plugin.openFiles(initialDirectory: "/example/directory"); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': "/example/directory", + 'confirmButtonText': null, + 'multiple': true, + }), + ], + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.openFiles(confirmButtonText: "Open File"); + + expect( + log, + [ + isMethodCall('openFile', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': null, + 'confirmButtonText': "Open File", + 'multiple': true, + }), + ], + ); + }); + }); + + group('#getSavePath', () { + test('passes the accepted type groups correctly', () async { + final group = XTypeGroup( + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + webWildCards: ['document/*']); + + final groupTwo = XTypeGroup( + label: 'image', + extensions: ['.jpg'], + mimeTypes: ['image/jpg'], + macUTIs: ['public.image'], + webWildCards: ['image/*']); + + await plugin.getSavePath(acceptedTypeGroups: [group, groupTwo]); + + expect( + log, + [ + isMethodCall('getSavePath', arguments: { + 'acceptedTypeGroups': [group.toJSON(), groupTwo.toJSON()], + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': null, + }), + ], + ); + }); + test('passes initialDirectory correctly', () async { + await plugin.getSavePath(initialDirectory: "/example/directory"); + + expect( + log, + [ + isMethodCall('getSavePath', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': "/example/directory", + 'suggestedName': null, + 'confirmButtonText': null, + }), + ], + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getSavePath(confirmButtonText: "Open File"); + + expect( + log, + [ + isMethodCall('getSavePath', arguments: { + 'acceptedTypeGroups': null, + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': "Open File", + }), + ], + ); + }); + group('#getDirectoryPath', () { + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPath(initialDirectory: "/example/directory"); + + expect( + log, + [ + isMethodCall('getDirectoryPath', arguments: { + 'initialDirectory': "/example/directory", + 'confirmButtonText': null, + }), + ], + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPath(confirmButtonText: "Open File"); + + expect( + log, + [ + isMethodCall('getDirectoryPath', arguments: { + 'initialDirectory': null, + 'confirmButtonText': "Open File", + }), + ], + ); + }); + }); + }); + }); +} diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart new file mode 100644 index 000000000000..71701a97a271 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart @@ -0,0 +1,38 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('chrome') // Uses web-only Flutter SDK + +import 'dart:convert'; +import 'dart:html' as html; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; + +final String expectedStringContents = 'Hello, world!'; +final Uint8List bytes = utf8.encode(expectedStringContents); +final html.File textFile = html.File([bytes], 'hello.txt'); +final String textFileUrl = html.Url.createObjectUrl(textFile); + +void main() { + group('Create with an objectUrl', () { + final file = XFile(textFileUrl); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); +} diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart new file mode 100644 index 000000000000..85c49fc81bab --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart @@ -0,0 +1,50 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('vm') // Uses dart:io + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; + +// Please note that executing this test with command +// `flutter test test/x_file_io_test.dart` will set the directory +// to ./file_selector_platform_interface. +// +// This will cause our hello.txt file to be not be found. Please +// execute this test with `flutter test` or change the path to +// ./test/assets/hello.txt +// +// https://github.com/flutter/flutter/issues/20907 + +final path = './assets/hello.txt'; +final String expectedStringContents = 'Hello, world!'; +final Uint8List bytes = utf8.encode(expectedStringContents); +final File textFile = File(path); +final String textFilePath = textFile.path; + +void main() { + group('Create with a path', () { + final pickedFile = XFile(textFilePath); + + test('Can be read as a string', () async { + expect(await pickedFile.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await pickedFile.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await pickedFile.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect( + await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); +} From d7afd5b46c320d017296e113a7c30977b470772a Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 16:28:47 -0700 Subject: [PATCH 04/13] Update year --- .../test/method_channel_file_selector_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart index 57638e2d6e7b..adb519248b3b 100644 --- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -1,4 +1,4 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. +// 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. From f3cb6652f97ea11d3e637496629ee076cf84333e Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 17:34:27 -0700 Subject: [PATCH 05/13] Add more XFile tests, fix dart io XFile saveTo(..) --- .../lib/src/types/x_file/io.dart | 4 +- .../test/x_file_html_test.dart | 70 +++++++++++++++++++ .../test/x_file_io_test.dart | 63 +++++++++++++++-- 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart index bd8470a1d8ca..2067bbdfb140 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart @@ -35,7 +35,7 @@ class XFile extends XFileBase { int length, DateTime lastModified, }) : _bytes = bytes, - _file = File(path), + _file = File(path ?? ''), _length = length, _lastModified = lastModified, super(path) { @@ -55,7 +55,7 @@ class XFile extends XFileBase { @override void saveTo(String path) async { File fileToSave = File(path); - await fileToSave.writeAsBytes(_bytes); + await fileToSave.writeAsBytes(_bytes ?? (await readAsBytes())); await fileToSave.create(); } diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart index 71701a97a271..08b9032006e9 100644 --- a/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart @@ -11,6 +11,8 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'dart:html'; + final String expectedStringContents = 'Hello, world!'; final Uint8List bytes = utf8.encode(expectedStringContents); final html.File textFile = html.File([bytes], 'hello.txt'); @@ -35,4 +37,72 @@ void main() { expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); }); }); + + group('Create from data', () { + final file = XFile.fromData(bytes); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); + + group('saveTo(..)', () { + final String xFileDomElementId = '__x_file_dom_element'; + + group('XFile saveTo(..)', () { + test('creates a DOM container', () async { + XFile file = XFile.fromData(bytes); + + await file.saveTo(''); + + final container = querySelector('#${xFileDomElementId}'); + + expect(container, isNotNull); + }); + + test('create anchor element', () async { + XFile file = XFile.fromData(bytes, name: textFile.name); + + await file.saveTo('path'); + + final container = querySelector('#${xFileDomElementId}'); + final AnchorElement element = container?.children?.firstWhere( + (element) => element.tagName == 'A', + orElse: () => null); + + expect(element, isNotNull); + expect(element.href, file.path); + expect(element.download, file.name); + }); + + test('anchor element is clicked', () async { + final mockAnchor = AnchorElement(); + + XFileTestOverrides overrides = XFileTestOverrides( + createAnchorElement: (_, __) => mockAnchor, + ); + + XFile file = + XFile.fromData(bytes, name: textFile.name, overrides: overrides); + + bool clicked = false; + mockAnchor.onClick.listen((event) => clicked = true); + + await file.saveTo('path'); + + expect(clicked, true); + }); + }); + }); } diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart index 85c49fc81bab..af9639a32c29 100644 --- a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart @@ -21,7 +21,8 @@ import 'package:file_selector_platform_interface/file_selector_platform_interfac // // https://github.com/flutter/flutter/issues/20907 -final path = './assets/hello.txt'; +final pathPrefix = './assets/'; +final path = pathPrefix + 'hello.txt'; final String expectedStringContents = 'Hello, world!'; final Uint8List bytes = utf8.encode(expectedStringContents); final File textFile = File(path); @@ -29,22 +30,70 @@ final String textFilePath = textFile.path; void main() { group('Create with a path', () { - final pickedFile = XFile(textFilePath); + final file = XFile(textFilePath); test('Can be read as a string', () async { - expect(await pickedFile.readAsString(), equals(expectedStringContents)); + expect(await file.readAsString(), equals(expectedStringContents)); }); test('Can be read as bytes', () async { - expect(await pickedFile.readAsBytes(), equals(bytes)); + expect(await file.readAsBytes(), equals(bytes)); }); test('Can be read as a stream', () async { - expect(await pickedFile.openRead().first, equals(bytes)); + expect(await file.openRead().first, equals(bytes)); }); test('Stream can be sliced', () async { - expect( - await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + + test('saveTo(..) creates file', () async { + File removeBeforeTest = File(pathPrefix + 'newFilePath.txt'); + if (removeBeforeTest.existsSync()) { + await removeBeforeTest.delete(); + } + + await file.saveTo(pathPrefix + 'newFilePath.txt'); + File newFile = File(pathPrefix + 'newFilePath.txt'); + + expect(newFile.existsSync(), isTrue); + expect(newFile.readAsStringSync(), 'Hello, world!'); + + await newFile.delete(); + }); + }); + + group('Create with data', () { + final file = XFile.fromData(bytes); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + + test('Function saveTo(..) creates file', () async { + File removeBeforeTest = File(pathPrefix + 'newFileData.txt'); + if (removeBeforeTest.existsSync()) { + await removeBeforeTest.delete(); + } + + await file.saveTo(pathPrefix + 'newFileData.txt'); + File newFile = File(pathPrefix + 'newFileData.txt'); + + expect(newFile.existsSync(), isTrue); + expect(newFile.readAsStringSync(), 'Hello, world!'); + + await newFile.delete(); }); }); } From 6bf7c99086946668f0f01615f1de745cacd4414b Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Thu, 3 Sep 2020 17:52:19 -0700 Subject: [PATCH 06/13] Update copyrights --- packages/file_selector/file_selector_platform_interface/LICENSE | 2 +- .../lib/src/method_channel/method_channel_file_selector.dart | 2 +- .../lib/src/platform_interface/file_selector_interface.dart | 2 +- .../test/file_selector_platform_interface_test.dart | 2 +- .../file_selector_platform_interface/test/x_file_html_test.dart | 2 +- .../file_selector_platform_interface/test/x_file_io_test.dart | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/LICENSE b/packages/file_selector/file_selector_platform_interface/LICENSE index 0c91662b3f2f..47bd8aed523c 100644 --- a/packages/file_selector/file_selector_platform_interface/LICENSE +++ b/packages/file_selector/file_selector_platform_interface/LICENSE @@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// 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 diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index 41ebdc9f3181..cc1264e520c6 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// 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. diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart index 675fe1ee2585..0f0cc0298777 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart @@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// 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. diff --git a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart index eb5bb89ad5b1..6809cee66963 100644 --- a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart @@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// 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. diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart index 08b9032006e9..f888a0486ca7 100644 --- a/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_html_test.dart @@ -1,4 +1,4 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// 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. diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart index af9639a32c29..518a44d4a7da 100644 --- a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// 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. From b3a65a3cadc14fecc49c71955dba874adca43307 Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Tue, 8 Sep 2020 13:05:09 -0700 Subject: [PATCH 07/13] Add XTypeGroup tests and remove document/* as a web wild card --- .../method_channel_file_selector_test.dart | 30 +++++++-------- .../test/x_file_io_test.dart | 4 +- .../test/x_type_group_test.dart | 37 +++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 packages/file_selector/file_selector_platform_interface/test/x_type_group_test.dart diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart index adb519248b3b..99f9fe0f0e3b 100644 --- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -28,11 +28,11 @@ void main() { group('#openFile', () { test('passes the accepted type groups correctly', () async { final group = XTypeGroup( - label: 'text', - extensions: ['.txt'], - mimeTypes: ['text/plain'], - macUTIs: ['public.text'], - webWildCards: ['document/*']); + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + ); final groupTwo = XTypeGroup( label: 'image', @@ -89,11 +89,11 @@ void main() { group('#openFiles', () { test('passes the accepted type groups correctly', () async { final group = XTypeGroup( - label: 'text', - extensions: ['.txt'], - mimeTypes: ['text/plain'], - macUTIs: ['public.text'], - webWildCards: ['document/*']); + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + ); final groupTwo = XTypeGroup( label: 'image', @@ -151,11 +151,11 @@ void main() { group('#getSavePath', () { test('passes the accepted type groups correctly', () async { final group = XTypeGroup( - label: 'text', - extensions: ['.txt'], - mimeTypes: ['text/plain'], - macUTIs: ['public.text'], - webWildCards: ['document/*']); + label: 'text', + extensions: ['.txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + ); final groupTwo = XTypeGroup( label: 'image', diff --git a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart index 518a44d4a7da..b669324462b2 100644 --- a/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/x_file_io_test.dart @@ -16,8 +16,8 @@ import 'package:file_selector_platform_interface/file_selector_platform_interfac // to ./file_selector_platform_interface. // // This will cause our hello.txt file to be not be found. Please -// execute this test with `flutter test` or change the path to -// ./test/assets/hello.txt +// execute this test with `flutter test` or change the path prefix +// to ./test/assets/ // // https://github.com/flutter/flutter/issues/20907 diff --git a/packages/file_selector/file_selector_platform_interface/test/x_type_group_test.dart b/packages/file_selector/file_selector_platform_interface/test/x_type_group_test.dart new file mode 100644 index 000000000000..877e530cb342 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/test/x_type_group_test.dart @@ -0,0 +1,37 @@ +// 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:file_selector_platform_interface/file_selector_platform_interface.dart'; + +void main() { + group('XTypeGroup', () { + test('fails assertion with no parameters set', () { + expect(() => XTypeGroup(), throwsAssertionError); + }); + + test('toJSON() creates correct map', () { + final label = 'test group'; + final extensions = ['.txt', '.jpg']; + final mimeTypes = ['text/plain']; + final macUTIs = ['public.plain-text']; + final webWildCards = ['image/*']; + + final group = XTypeGroup( + label: label, + extensions: extensions, + mimeTypes: mimeTypes, + macUTIs: macUTIs, + webWildCards: webWildCards, + ); + + final jsonMap = group.toJSON(); + expect(jsonMap['label'], label); + expect(jsonMap['extensions'], extensions); + expect(jsonMap['mimeTypes'], mimeTypes); + expect(jsonMap['macUTIs'], macUTIs); + expect(jsonMap['webWildCards'], webWildCards); + }); + }); +} From 2430dbde53c5b4441a6aa273a5c19dc357fc0f38 Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Tue, 8 Sep 2020 13:59:23 -0700 Subject: [PATCH 08/13] Update License --- .../file_selector_platform_interface/LICENSE | 52 +++++++++---------- .../src/types/x_type_group/x_type_group.dart | 16 ++---- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/LICENSE b/packages/file_selector/file_selector_platform_interface/LICENSE index 47bd8aed523c..19b81e5dac6c 100644 --- a/packages/file_selector/file_selector_platform_interface/LICENSE +++ b/packages/file_selector/file_selector_platform_interface/LICENSE @@ -1,27 +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. +Copyright 2020 The Chromium 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_platform_interface/lib/src/types/x_type_group/x_type_group.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart index b14a3fa235b2..a7d52a766498 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart @@ -1,16 +1,6 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// 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. /// A set of allowed XTypes class XTypeGroup { From 185b59cb1e7acceb472f15fb0fbe6ddaf3e64c7d Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Tue, 8 Sep 2020 14:19:10 -0700 Subject: [PATCH 09/13] Update License (Flutter Authors) --- packages/file_selector/file_selector_platform_interface/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/file_selector/file_selector_platform_interface/LICENSE b/packages/file_selector/file_selector_platform_interface/LICENSE index 19b81e5dac6c..2c91f1438173 100644 --- a/packages/file_selector/file_selector_platform_interface/LICENSE +++ b/packages/file_selector/file_selector_platform_interface/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020 The Chromium Authors. All rights reserved. +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: From 7c947e1ff8f201adc170be65d8d315a841bdf36b Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Tue, 8 Sep 2020 17:59:29 -0700 Subject: [PATCH 10/13] Change to version 1.0.0, rename unsupported.dart, fix references to "file_picker" --- .../file_selector_platform_interface/CHANGELOG.md | 2 +- .../lib/src/method_channel/method_channel_file_selector.dart | 2 +- .../lib/src/platform_interface/file_selector_interface.dart | 4 ++-- .../lib/src/types/x_file/{unsupported.dart => interface.dart} | 0 .../lib/src/types/x_file/x_file.dart | 2 +- .../file_selector_platform_interface/pubspec.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/{unsupported.dart => interface.dart} (100%) diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index 6073234226b2..0d8803f93540 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.1.0 +## 1.0.0 * Initial release. diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index cc1264e520c6..9dd32b9dd245 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -7,7 +7,7 @@ import 'package:flutter/services.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:meta/meta.dart'; -const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_picker'); +const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_selector'); /// An implementation of [FileSelectorPlatform] that uses method channels. class MethodChannelFileSelector extends FileSelectorPlatform { diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart index 0f0cc0298777..cf23d5f01eab 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart @@ -9,9 +9,9 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../method_channel/method_channel_file_selector.dart'; -/// The interface that implementations of file_picker must implement. +/// The interface that implementations of file_selector must implement. /// -/// Platform implementations should extend this class rather than implement it as `file_picker` +/// Platform implementations should extend this class rather than implement it as `file_selector` /// 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 diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/interface.dart similarity index 100% rename from packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/unsupported.dart rename to packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/interface.dart diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart index f966a7c9a3aa..4545c605875a 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/x_file.dart @@ -1,3 +1,3 @@ -export 'unsupported.dart' +export 'interface.dart' if (dart.library.html) 'html.dart' if (dart.library.io) 'io.dart'; diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index be24867c84bc..015762a8f5a1 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the file_selector plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 0.1.0 +version: 1.0.0 dependencies: flutter: From 942c9d218e2b305dc5c0a0319a4c480efb77d70d Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Tue, 8 Sep 2020 18:10:49 -0700 Subject: [PATCH 11/13] Update openRead(..) function --- .../lib/src/types/x_file/io.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart index 2067bbdfb140..753732df2811 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_file/io.dart @@ -93,14 +93,19 @@ class XFile extends XFileBase { return _file.readAsBytes(); } + Stream _getBytes(int start, int end) async* { + final bytes = _bytes; + yield bytes.sublist(start ?? 0, end ?? bytes.length); + } + @override - Stream openRead([int start, int end]) async* { + Stream openRead([int start, int end]) { if (_bytes != null) { - final bytes = _bytes; - yield bytes.sublist(start ?? 0, end ?? bytes.length); + return _getBytes(start, end); + } else { + return _file + .openRead(start ?? 0, end) + .map((chunk) => Uint8List.fromList(chunk)); } - yield* _file - .openRead(start ?? 0, end) - .map((chunk) => Uint8List.fromList(chunk)); } } From 2a3f0ba1156e528d38be3bb1253d0a283c10a8aa Mon Sep 17 00:00:00 2001 From: Jason Panelli Date: Wed, 9 Sep 2020 11:35:18 -0700 Subject: [PATCH 12/13] Run flutter format --- .../lib/src/method_channel/method_channel_file_selector.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index 9dd32b9dd245..8681a1dbffa6 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -7,7 +7,8 @@ import 'package:flutter/services.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:meta/meta.dart'; -const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_selector'); +const MethodChannel _channel = + MethodChannel('plugins.flutter.io/file_selector'); /// An implementation of [FileSelectorPlatform] that uses method channels. class MethodChannelFileSelector extends FileSelectorPlatform { From ea07c3f848c0481dfc060394ae4f97e685596ff8 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 24 Sep 2020 12:39:01 -0700 Subject: [PATCH 13/13] Add codeowners for the file_selector plugin. --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 2d4bff36c9eb..160c0d5a3d10 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -11,6 +11,7 @@ packages/camera/** @bparrishMines packages/connectivity/** @cyanglaz @matthew-carroll packages/device_info/** @matthew-carroll packages/espresso/** @collinjackson @adazh +packages/file_selector/** @ditman packages/google_maps_flutter/** @cyanglaz packages/google_sign_in/** @cyanglaz @mehmetf packages/image_picker/** @cyanglaz