diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 164a5650194..8bd0909d3dd 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.6 + +* Separate "Save As" implementation details from XFile web class. + ## 0.3.5 * Fixes a bug where the bytes of an XFile, that is created using the `XFile.fromData` constructor, are ignored on web. diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index 33e937b59bf..8ac7b6a8b51 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -7,7 +7,6 @@ import 'dart:convert'; import 'dart:js_interop'; import 'dart:typed_data'; -import 'package:meta/meta.dart'; import 'package:web/web.dart'; import '../web_helpers/web_helpers.dart'; @@ -35,11 +34,9 @@ class XFile extends XFileBase { int? length, Uint8List? bytes, DateTime? lastModified, - @visibleForTesting CrossFileTestOverrides? overrides, }) : _mimeType = mimeType, _path = path, _length = length, - _overrides = overrides, _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), _name = name ?? '', super(path) { @@ -57,10 +54,8 @@ class XFile extends XFileBase { int? length, DateTime? lastModified, String? path, - @visibleForTesting CrossFileTestOverrides? overrides, }) : _mimeType = mimeType, _length = length, - _overrides = overrides, _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), _name = name ?? '', super(path) { @@ -82,12 +77,16 @@ class XFile extends XFileBase { // MimeType of the file (eg: "image/gif"). final String? _mimeType; + // Name (with extension) of the file (eg: "anim.gif") final String _name; + // Path of the file (must be a valid Blob URL, when set manually!) late String _path; + // The size of the file (in bytes). final int? _length; + // The time the file was last modified. final DateTime _lastModified; @@ -97,17 +96,6 @@ class XFile extends XFileBase { // (Similar to a (read-only) dart:io File.) Blob? _browserBlob; - // An html Element that will be used to trigger a "save as" dialog later. - // TODO(dit): https://github.com/flutter/flutter/issues/91400 Remove this _target. - late Element _target; - - // Overrides for testing - // TODO(dit): https://github.com/flutter/flutter/issues/91400 Remove these _overrides, - // they're only used to Save As... - final CrossFileTestOverrides? _overrides; - - bool get _hasTestOverrides => _overrides != null; - @override String? get mimeType => _mimeType; @@ -203,37 +191,8 @@ class XFile extends XFileBase { /// Saves the data of this CrossFile at the location indicated by path. /// For the web implementation, the path variable is ignored. - // TODO(dit): https://github.com/flutter/flutter/issues/91400 - // Move implementation to web_helpers.dart @override Future saveTo(String path) async { - // Create a DOM container where the anchor can be injected. - _target = ensureInitialized('__x_file_dom_element'); - - // Create an tag with the appropriate download attributes and click it - // May be overridden with CrossFileTestOverrides - final HTMLAnchorElement element = - _hasTestOverrides - ? _overrides!.createAnchorElement(this.path, name) - as HTMLAnchorElement - : createAnchorElement(this.path, name); - - // Clear the children in _target and add an element to click - while (_target.children.length > 0) { - _target.removeChild(_target.children.item(0)!); - } - addElementToContainerAndClick(_target, element); + await saveFileAs(this, path); } } - -/// Overrides some functions to allow testing -// TODO(dit): https://github.com/flutter/flutter/issues/91400 -// Move this to web_helpers_test.dart -@visibleForTesting -class CrossFileTestOverrides { - /// Default constructor for overrides - CrossFileTestOverrides({required this.createAnchorElement}); - - /// For overriding the creation of the file input element. - Element Function(String href, String suggestedName) createAnchorElement; -} diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart index daac0315950..1ee3594869d 100644 --- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart +++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart @@ -3,6 +3,14 @@ // found in the LICENSE file. import 'package:web/web.dart'; +import '../types/html.dart'; + +/// Type definition for function that creates anchor elements +typedef CreateAnchorElement = + HTMLAnchorElement Function(String href, String? suggestedName); + +/// Override for creating anchor elements for testing purposes +CreateAnchorElement? anchorElementOverride; /// Create anchor element with download attribute HTMLAnchorElement createAnchorElement(String href, String? suggestedName) => @@ -35,3 +43,23 @@ Element ensureInitialized(String id) { bool isSafari() { return window.navigator.vendor == 'Apple Computer, Inc.'; } + +/// Saves the given [XFile] to user's device ("Save As" dialog). +Future saveFileAs(XFile file, String path) async { + // Create container element. + final Element target = ensureInitialized('__x_file_dom_element'); + + // Create element. + final HTMLAnchorElement element = + anchorElementOverride != null + ? anchorElementOverride!(file.path, file.name) + : createAnchorElement(file.path, file.name); + + // Clear existing children before appending new one. + while (target.children.length > 0) { + target.removeChild(target.children.item(0)!); + } + + // Add and click. + addElementToContainerAndClick(target, element); +} diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index 5be291469c2..9d6c3275c04 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -2,7 +2,7 @@ name: cross_file description: An abstraction to allow working with files across multiple platforms. repository: https://github.com/flutter/packages/tree/main/packages/cross_file issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 -version: 0.3.5 +version: 0.3.6 environment: sdk: ^3.7.0 diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart index cd938be1e70..3241c4efaba 100644 --- a/packages/cross_file/test/x_file_html_test.dart +++ b/packages/cross_file/test/x_file_html_test.dart @@ -10,6 +10,7 @@ import 'dart:js_interop'; import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; +import 'package:cross_file/src/web_helpers/web_helpers.dart' as helpers; import 'package:test/test.dart'; import 'package:web/web.dart' as html; @@ -147,15 +148,9 @@ void main() { final html.HTMLAnchorElement mockAnchor = html.document.createElement('a') as html.HTMLAnchorElement; - final CrossFileTestOverrides overrides = CrossFileTestOverrides( - createAnchorElement: (_, __) => mockAnchor, - ); + helpers.anchorElementOverride = (_, __) => mockAnchor; - final XFile file = XFile.fromData( - bytes, - name: textFile.name, - overrides: overrides, - ); + final XFile file = XFile.fromData(bytes, name: textFile.name); bool clicked = false; mockAnchor.onClick.listen((html.MouseEvent event) => clicked = true);