Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cross_file] Move from flutter/plugins. #305

Merged
merged 15 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/cross_file/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.1+1

* Rehomed to `flutter/packages` repository.

## 0.3.1

* Fix nullability of `XFileBase`'s `path` and `name` to match the
Expand Down
1 change: 1 addition & 0 deletions packages/cross_file/lib/src/types/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'dart:typed_data';
/// the methods should seem familiar.
abstract class XFileBase {
/// Construct a CrossFile
// ignore: avoid_unused_constructor_parameters
XFileBase(String? path);

/// Save the CrossFile at the indicated file path.
Expand Down
60 changes: 34 additions & 26 deletions packages/cross_file/lib/src/types/html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,13 @@ import 'dart:typed_data';

import 'package:meta/meta.dart';

import './base.dart';
import '../web_helpers/web_helpers.dart';
import './base.dart';

/// A CrossFile that works on web.
///
/// It wraps the bytes of a selected file.
class XFile extends XFileBase {
late String path;

final String? mimeType;
final Uint8List? _data;
final int? _length;
final String name;
final DateTime? _lastModified;

late Element _target;

final CrossFileTestOverrides? _overrides;

bool get _hasTestOverrides => _overrides != null;

/// Construct a CrossFile object from its ObjectUrl.
///
/// Optionally, this can be initialized with `bytes` and `length`
Expand Down Expand Up @@ -67,23 +53,43 @@ class XFile extends XFileBase {
name = name ?? '',
super(path) {
if (path == null) {
final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType);
final Blob blob = (mimeType == null)
? Blob(<dynamic>[bytes])
: Blob(<dynamic>[bytes], mimeType);
this.path = Url.createObjectUrl(blob);
} else {
this.path = path;
}
}

@override
Future<DateTime> lastModified() async => Future.value(_lastModified);
final String? mimeType;
@override
final String name;
@override
late String path;

final Uint8List? _data;
final int? _length;
final DateTime? _lastModified;

late Element _target;

final CrossFileTestOverrides? _overrides;

bool get _hasTestOverrides => _overrides != null;

@override
Future<DateTime> lastModified() async =>
Future<DateTime>.value(_lastModified);

Future<Uint8List> get _bytes async {
if (_data != null) {
return Future.value(UnmodifiableUint8ListView(_data!));
return Future<Uint8List>.value(UnmodifiableUint8ListView(_data!));
}

// We can force 'response' to be a byte buffer by passing responseType:
ByteBuffer? response =
final ByteBuffer? response =
(await HttpRequest.request(path, responseType: 'arraybuffer')).response;

return response?.asUint8List() ?? Uint8List(0);
Expand All @@ -98,25 +104,27 @@ class XFile extends XFileBase {
}

@override
Future<Uint8List> readAsBytes() async => Future.value(await _bytes);
Future<Uint8List> readAsBytes() async =>
Future<Uint8List>.value(await _bytes);

@override
Stream<Uint8List> openRead([int? start, int? end]) async* {
final bytes = await _bytes;
final Uint8List bytes = await _bytes;
yield bytes.sublist(start ?? 0, end ?? bytes.length);
}

/// Saves the data of this CrossFile at the location indicated by path.
/// For the web implementation, the path variable is ignored.
@override
Future<void> saveTo(String path) async {
// Create a DOM container where we can host the anchor.
_target = ensureInitialized('__x_file_dom_element');

// Create an <a> tag with the appropriate download attributes and click it
// May be overridden with CrossFileTestOverrides
final AnchorElement element = _hasTestOverrides
? _overrides!.createAnchorElement(this.path, this.name) as AnchorElement
: createAnchorElement(this.path, this.name);
? _overrides!.createAnchorElement(this.path, name) as AnchorElement
: createAnchorElement(this.path, name);

// Clear the children in our container so we can add an element to click
_target.children.clear();
Expand All @@ -127,9 +135,9 @@ class XFile extends XFileBase {
/// Overrides some functions to allow testing
@visibleForTesting
class CrossFileTestOverrides {
/// For overriding the creation of the file input element.
Element Function(String href, String suggestedName) createAnchorElement;

/// Default constructor for overrides
CrossFileTestOverrides({required this.createAnchorElement});

/// For overriding the creation of the file input element.
Element Function(String href, String suggestedName) createAnchorElement;
}
8 changes: 5 additions & 3 deletions packages/cross_file/lib/src/types/interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'package:meta/meta.dart';

import './base.dart';

// ignore_for_file: avoid_unused_constructor_parameters

/// A CrossFile is a cross-platform, simplified File abstraction.
///
/// It wraps the bytes of a selected file, and its (platform-dependant) path.
Expand Down Expand Up @@ -50,9 +52,9 @@ class XFile extends XFileBase {
/// Overrides some functions of CrossFile for testing purposes
@visibleForTesting
class CrossFileTestOverrides {
/// For overriding the creation of the file input element.
dynamic Function(String href, String suggestedName) createAnchorElement;

/// Default constructor for overrides
CrossFileTestOverrides({required this.createAnchorElement});

/// For overriding the creation of the file input element.
dynamic Function(String href, String suggestedName) createAnchorElement;
}
32 changes: 18 additions & 14 deletions packages/cross_file/lib/src/types/io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@ import 'dart:typed_data';

import './base.dart';

// ignore_for_file: avoid_unused_constructor_parameters

/// A CrossFile 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 CrossFile object backed by a dart:io File.
XFile(
String path, {
Expand Down Expand Up @@ -48,17 +43,26 @@ class XFile extends XFileBase {
}
}

final File _file;
@override
final String? mimeType;
final DateTime? _lastModified;
int? _length;

final Uint8List? _bytes;

@override
Future<DateTime> lastModified() {
if (_lastModified != null) {
return Future.value(_lastModified);
return Future<DateTime>.value(_lastModified);
}
// ignore: avoid_slow_async_io
return _file.lastModified();
}

@override
Future<void> saveTo(String path) async {
File fileToSave = File(path);
final File fileToSave = File(path);
await fileToSave.writeAsBytes(_bytes ?? (await readAsBytes()));
await fileToSave.create();
}
Expand All @@ -76,29 +80,29 @@ class XFile extends XFileBase {
@override
Future<int> length() {
if (_length != null) {
return Future.value(_length);
return Future<int>.value(_length);
}
return _file.length();
}

@override
Future<String> readAsString({Encoding encoding = utf8}) {
if (_bytes != null) {
return Future.value(String.fromCharCodes(_bytes!));
return Future<String>.value(String.fromCharCodes(_bytes!));
}
return _file.readAsString(encoding: encoding);
}

@override
Future<Uint8List> readAsBytes() {
if (_bytes != null) {
return Future.value(_bytes);
return Future<Uint8List>.value(_bytes);
}
return _file.readAsBytes();
}

Stream<Uint8List> _getBytes(int? start, int? end) async* {
final bytes = _bytes!;
final Uint8List bytes = _bytes!;
yield bytes.sublist(start ?? 0, end ?? bytes.length);
}

Expand All @@ -109,7 +113,7 @@ class XFile extends XFileBase {
} else {
return _file
.openRead(start ?? 0, end)
.map((chunk) => Uint8List.fromList(chunk));
.map((List<int> chunk) => Uint8List.fromList(chunk));
}
}
}
6 changes: 3 additions & 3 deletions packages/cross_file/lib/src/web_helpers/web_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import 'dart:html';

/// Create anchor element with download attribute
AnchorElement createAnchorElement(String href, String suggestedName) {
final element = AnchorElement(href: href);
AnchorElement createAnchorElement(String href, String? suggestedName) {
final AnchorElement element = AnchorElement(href: href);

if (suggestedName == null) {
element.download = 'download';
Expand All @@ -27,7 +27,7 @@ void addElementToContainerAndClick(Element container, Element element) {

/// Initializes a DOM container where we can host elements.
Element ensureInitialized(String id) {
var target = querySelector('#${id}');
Element? target = querySelector('#$id');
if (target == null) {
final Element targetElement = Element.tag('flt-x-file')..id = id;

Expand Down
6 changes: 3 additions & 3 deletions packages/cross_file/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: cross_file
description: An abstraction to allow working with files across multiple platforms.
homepage: https://github.com/flutter/plugins/tree/master/packages/cross_file
version: 0.3.1
repository: https://github.com/flutter/packages/tree/master/packages/cross_file
version: 0.3.1+1

dependencies:
flutter:
Expand All @@ -14,5 +14,5 @@ dev_dependencies:
pedantic: ^1.10.0

environment:
sdk: ">=2.12.0-259.9.beta <3.0.0"
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.22.0"
34 changes: 18 additions & 16 deletions packages/cross_file/test/x_file_html_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:cross_file/cross_file.dart';

final String expectedStringContents = 'Hello, world!';
const String expectedStringContents = 'Hello, world!';
final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents));
final html.File textFile = html.File([bytes], 'hello.txt');
final html.File textFile = html.File(<Object>[bytes], 'hello.txt');
final String textFileUrl = html.Url.createObjectUrl(textFile);

void main() {
group('Create with an objectUrl', () {
final file = XFile(textFileUrl);
final XFile file = XFile(textFileUrl);

test('Can be read as a string', () async {
expect(await file.readAsString(), equals(expectedStringContents));
Expand All @@ -37,7 +37,7 @@ void main() {
});

group('Create from data', () {
final file = XFile.fromData(bytes);
final XFile file = XFile.fromData(bytes);

test('Can be read as a string', () async {
expect(await file.readAsString(), equals(expectedStringContents));
Expand All @@ -56,46 +56,48 @@ void main() {
});

group('saveTo(..)', () {
final String CrossFileDomElementId = '__x_file_dom_element';
const String crossFileDomElementId = '__x_file_dom_element';

group('CrossFile saveTo(..)', () {
test('creates a DOM container', () async {
XFile file = XFile.fromData(bytes);
final XFile file = XFile.fromData(bytes);

await file.saveTo('');

final container = html.querySelector('#${CrossFileDomElementId}');
final html.Element? container =
html.querySelector('#$crossFileDomElementId');

expect(container, isNotNull);
});

test('create anchor element', () async {
XFile file = XFile.fromData(bytes, name: textFile.name);
final XFile file = XFile.fromData(bytes, name: textFile.name);

await file.saveTo('path');

final container = html.querySelector('#${CrossFileDomElementId}');
final html.AnchorElement element =
container?.children.firstWhere((element) => element.tagName == 'A')
as html.AnchorElement;
final html.Element? container =
html.querySelector('#$crossFileDomElementId');
final html.AnchorElement element = container?.children
.firstWhere((html.Element element) => element.tagName == 'A')
as html.AnchorElement;

// if element is not found, the `firstWhere` call will throw StateError.
expect(element.href, file.path);
expect(element.download, file.name);
});

test('anchor element is clicked', () async {
final mockAnchor = html.AnchorElement();
final html.AnchorElement mockAnchor = html.AnchorElement();

CrossFileTestOverrides overrides = CrossFileTestOverrides(
final CrossFileTestOverrides overrides = CrossFileTestOverrides(
createAnchorElement: (_, __) => mockAnchor,
);

XFile file =
final XFile file =
XFile.fromData(bytes, name: textFile.name, overrides: overrides);

bool clicked = false;
mockAnchor.onClick.listen((event) => clicked = true);
mockAnchor.onClick.listen((html.MouseEvent event) => clicked = true);

await file.saveTo('path');

Expand Down
Loading