diff --git a/packages/cross_file/cross_file_platform_interface/AUTHORS b/packages/cross_file/cross_file_platform_interface/AUTHORS new file mode 100644 index 000000000000..a4a27429da16 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google LLC diff --git a/packages/cross_file/cross_file_platform_interface/CHANGELOG.md b/packages/cross_file/cross_file_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..6073234226b2 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial release. diff --git a/packages/cross_file/cross_file_platform_interface/LICENSE b/packages/cross_file/cross_file_platform_interface/LICENSE new file mode 100644 index 000000000000..29b709dac6c7 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors + +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/cross_file/cross_file_platform_interface/README.md b/packages/cross_file/cross_file_platform_interface/README.md new file mode 100644 index 000000000000..bbe36d350d9e --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/README.md @@ -0,0 +1,21 @@ +# cross_file_platform_interface + +A common platform interface for the [`cross_file`](https://pub.dev/packages/cross_file) plugin. + +This interface allows platform implementations of the `cross_file` plugin, as well as the plugin +itself, to ensure they are supporting the same interface. + +# Usage + +To implement a new platform implementation of `cross_file`, extend +[`CrossFilePlatform`](lib/src/cross_file_platform.dart) with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`CrossFilePlatform` by calling `CrossFilePlatform.instance = CrossFileMyPlatform()`. + +# 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. diff --git a/packages/cross_file/cross_file_platform_interface/lib/cross_file_platform_interface.dart b/packages/cross_file/cross_file_platform_interface/lib/cross_file_platform_interface.dart new file mode 100644 index 000000000000..790f897a7bd9 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/cross_file_platform_interface.dart @@ -0,0 +1,12 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/cross_file_platform.dart'; +export 'src/file_system/platform_file_system_cross_directory.dart'; +export 'src/file_system/platform_file_system_cross_file.dart'; +export 'src/platform_cross_directory.dart'; +export 'src/platform_cross_entity.dart'; +export 'src/platform_cross_file.dart'; +export 'src/scoped_storage/platform_scoped_storage_cross_directory.dart'; +export 'src/scoped_storage/platform_scoped_storage_cross_file.dart'; diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/cross_file_platform.dart b/packages/cross_file/cross_file_platform_interface/lib/src/cross_file_platform.dart new file mode 100644 index 000000000000..244c48ab8aad --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/cross_file_platform.dart @@ -0,0 +1,161 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'file_system/platform_file_system_cross_directory.dart'; +import 'file_system/platform_file_system_cross_file.dart'; +import 'platform_cross_directory.dart'; +import 'platform_cross_entity.dart'; +import 'scoped_storage/platform_scoped_storage_cross_directory.dart'; +import 'scoped_storage/platform_scoped_storage_cross_file.dart'; + +/// Interface for a platform implementation of `cross_file`. +abstract base class CrossFilePlatform { + /// The instance of [CrossFilePlatform] to be used. + /// + /// Platform implementations packages should set this with their own + /// implementation of [CrossFilePlatform] when they register themselves. + static CrossFilePlatform? instance; + + /// Creates a new [PlatformFileSystemXFile]. + PlatformFileSystemXFile createPlatformFileSystemXFile( + PlatformFileSystemXFileCreationParams params, + ) { + return _DefaultFileSystemXFile(params); + } + + /// Creates a new [PlatformFileSystemXDirectory]. + PlatformFileSystemXDirectory createPlatformFileSystemXDirectory( + PlatformFileSystemXDirectoryCreationParams params, + ) { + return _DefaultFileSystemXDirectory(params); + } + + /// Creates a new [PlatformScopedStorageXFile]. + PlatformScopedStorageXFile createPlatformScopedStorageXFile( + PlatformScopedStorageXFileCreationParams params, + ) { + return _DefaultScopedStorageXFile(params); + } + + /// Creates a new [PlatformScopedStorageXDirectory]. + PlatformScopedStorageXDirectory createPlatformScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams params, + ) { + return _DefaultScopedStorageXDirectory(params); + } +} + +/// Implementation of [PlatformFileSystemXFile] that represents a resource that +/// does not exist. +final class _DefaultFileSystemXFile extends PlatformFileSystemXFile { + _DefaultFileSystemXFile(super.params) : super.implementation(); + + @override + Future exists() async => false; + + @override + Future lastModified() async => null; + + @override + Future length() async => null; + + @override + Future name() async => null; + + @override + Stream openRead([int? start, int? end]) async* { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future readAsBytes() { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future writeAsBytes(PlatformWriteAsBytesParams params) { + throw UnsupportedError('This instance does not represent any resource.'); + } +} + +/// Implementation of [PlatformFileSystemXDirectory] that represents a directory +/// that does not exist. +final class _DefaultFileSystemXDirectory extends PlatformFileSystemXDirectory { + _DefaultFileSystemXDirectory(super.params) : super.implementation(); + + @override + Future exists() async => false; + + @override + Stream list(PlatformListParams params) async* { + throw UnsupportedError('This instance does not represent any directory.'); + } +} + +/// Implementation of [PlatformScopedStorageXFile] that represents a resource +/// that does not exist. +final class _DefaultScopedStorageXFile extends PlatformScopedStorageXFile { + _DefaultScopedStorageXFile(super.params) : super.implementation(); + + @override + Future canRead() async => false; + + @override + Future exists() async => false; + + @override + Future lastModified() async => null; + + @override + Future length() async => null; + + @override + Future name() async => null; + + @override + Stream openRead([int? start, int? end]) async* { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future readAsBytes() { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + throw UnsupportedError('This instance does not represent any resource.'); + } + + @override + Future dispose() async {} +} + +/// Implementation of [PlatformScopedStorageXDirectory] that represents a +/// directory that does not exist. +final class _DefaultScopedStorageXDirectory extends PlatformScopedStorageXDirectory { + _DefaultScopedStorageXDirectory(super.params) : super.implementation(); + + @override + Future exists() async => false; + + @override + Future canRead() async => false; + + @override + Stream list(PlatformListParams params) async* { + throw UnsupportedError('This instance does not represent any directory.'); + } + + @override + Future dispose() async {} +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_directory.dart b/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_directory.dart new file mode 100644 index 000000000000..bca36a6ae026 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_directory.dart @@ -0,0 +1,110 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import '../cross_file_platform.dart'; +import '../platform_cross_directory.dart'; + +/// Object specifying creation parameters for creating a [PlatformFileSystemXDirectory]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformFileSystemXDirectoryCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformFileSystemXDirectoryCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidFileSystemXDirectoryCreationParams +/// extends PlatformFileSystemXDirectoryCreationParams { +/// AndroidFileSystemXDirectoryCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidFileSystemXDirectoryCreationParams.fromCreationParams( +/// PlatformFileSystemXDirectoryCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidFileSystemXDirectoryCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformFileSystemXDirectoryCreationParams extends PlatformXDirectoryCreationParams { + /// Constructs a [PlatformFileSystemXDirectoryCreationParams]. + PlatformFileSystemXDirectoryCreationParams(this.path) + : super( + uri: Uri.directory( + path, + windows: defaultTargetPlatform == TargetPlatform.windows, + ).toString(), + ); + + /// The path of the directory. + final String path; +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformFileSystemXDirectory]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformFileSystemXDirectory.extension]. +/// +/// ```dart +/// base class AndroidFileSystemXDirectory extends PlatformFileSystemXDirectory with AndroidFileSystemXDirectoryExtension { +/// // ... +/// @override +/// PlatformFileSystemXDirectoryExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidFileSystemXDirectoryExtension implements PlatformFileSystemXDirectoryExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformFileSystemXDirectoryExtension implements PlatformXDirectoryExtension {} + +/// Interface for a reference to a directory (or folder) on the file system. +abstract base class PlatformFileSystemXDirectory extends PlatformXDirectory { + /// Creates a new [PlatformFileSystemXDirectory] + factory PlatformFileSystemXDirectory(PlatformFileSystemXDirectoryCreationParams params) { + assert( + CrossFilePlatform.instance != null, + 'A platform implementation for `cross_file` has not been set. Please ' + 'ensure that an implementation of `CrossFilePlatform` has been set to ' + '`CrossFilePlatform.instance` before use. For unit testing, ' + '`CrossFilePlatform.instance` can be set with your own test implementation.', + ); + return CrossFilePlatform.instance!.createPlatformFileSystemXDirectory(params); + } + + /// Used by the platform implementation to create a new + /// [PlatformFileSystemXDirectory]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformFileSystemXDirectory.implementation( + PlatformFileSystemXDirectoryCreationParams super.params, + ); + + @override + PlatformFileSystemXDirectoryCreationParams get params => + super.params as PlatformFileSystemXDirectoryCreationParams; + + /// Extension for providing platform-specific features. + @override + PlatformFileSystemXDirectoryExtension? get extension => null; +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_file.dart b/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_file.dart new file mode 100644 index 000000000000..156aeda9aac6 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/file_system/platform_file_system_cross_file.dart @@ -0,0 +1,121 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import '../cross_file_platform.dart'; +import '../platform_cross_file.dart'; + +/// Object specifying creation parameters for creating a [PlatformFileSystemXFile]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformFileSystemXFileCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformFileSystemXFileCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidFileSystemXFileCreationParams +/// extends PlatformFileSystemXFileCreationParams { +/// AndroidFileSystemXFileCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidFileSystemXFileCreationParams.fromCreationParams( +/// PlatformFileSystemXFileCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidFileSystemXFileCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformFileSystemXFileCreationParams extends PlatformXFileCreationParams { + /// Constructs a [PlatformFileSystemXFileCreationParams]. + PlatformFileSystemXFileCreationParams(this.path) + : super( + uri: Uri.file(path, windows: defaultTargetPlatform == TargetPlatform.windows).toString(), + ); + + /// The path of the file. + final String path; +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformFileSystemXFile]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformFileSystemXFile.extension]. +/// +/// ```dart +/// base class AndroidFileSystemXFile extends PlatformFileSystemXFile with AndroidFileSystemXFileExtension { +/// // ... +/// @override +/// PlatformFileSystemXFileExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidFileSystemXFileExtension implements PlatformFileSystemXFileExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformFileSystemXFileExtension implements PlatformXFileExtension {} + +/// Interface for a reference to a local data resource on the file system. +abstract base class PlatformFileSystemXFile extends PlatformXFile { + /// Creates a new [PlatformFileSystemXFile] + factory PlatformFileSystemXFile(PlatformFileSystemXFileCreationParams params) { + assert( + CrossFilePlatform.instance != null, + 'A platform implementation for `cross_file` has not been set. Please ' + 'ensure that an implementation of `CrossFilePlatform` has been set to ' + '`CrossFilePlatform.instance` before use. For unit testing, ' + '`CrossFilePlatform.instance` can be set with your own test implementation.', + ); + return CrossFilePlatform.instance!.createPlatformFileSystemXFile(params); + } + + /// Used by the platform implementation to create a new + /// [PlatformFileSystemXFile]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformFileSystemXFile.implementation(PlatformFileSystemXFileCreationParams super.params); + + @override + PlatformFileSystemXFileCreationParams get params => + super.params as PlatformFileSystemXFileCreationParams; + + /// Extension for providing platform-specific features. + @override + PlatformFileSystemXFileExtension? get extension => null; + + /// Writes a list of bytes to a file. + /// + /// Platforms may throw an exception if there is an error opening or writing + /// to the file. + Future writeAsBytes(PlatformWriteAsBytesParams params); +} + +/// Base class for parameters passed to [PlatformFileSystemXFile.writeAsBytes]. +@immutable +base class PlatformWriteAsBytesParams { + /// Constructs a [PlatformWriteAsBytesParams]. + const PlatformWriteAsBytesParams(this.bytes); + + /// List of bytes to write to the file. + final Uint8List bytes; +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_directory.dart b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_directory.dart new file mode 100644 index 000000000000..ed1097522b3a --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_directory.dart @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors +// 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:flutter/foundation.dart' show immutable, protected; + +import 'platform_cross_entity.dart'; + +/// Object specifying creation parameters for creating a [PlatformXDirectory]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformXDirectoryCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformXDirectoryCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidXDirectoryCreationParams +/// extends PlatformXDirectoryCreationParams { +/// AndroidXDirectoryCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidXDirectoryCreationParams.fromCreationParams( +/// PlatformXDirectoryCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidXDirectoryCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformXDirectoryCreationParams extends PlatformXEntityCreationParams { + /// Constructs a [PlatformXDirectoryCreationParams]. + const PlatformXDirectoryCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformXDirectory]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformXDirectory.extension]. +/// +/// ```dart +/// base class AndroidXDirectory extends PlatformXDirectory with AndroidXDirectoryExtension { +/// // ... +/// @override +/// PlatformXDirectoryExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidXDirectoryExtension implements PlatformXDirectoryExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformXDirectoryExtension implements PlatformXEntityExtension {} + +/// Interface for a reference to a container of local data resources. +abstract base class PlatformXDirectory extends PlatformXEntity { + /// Constructs a [PlatformXDirectory]. + @protected + PlatformXDirectory(PlatformXDirectoryCreationParams super.params); + + @override + PlatformXDirectoryCreationParams get params => super.params as PlatformXDirectoryCreationParams; + + /// Extension for providing platform-specific features. + @override + PlatformXDirectoryExtension? get extension => null; + + /// Lists the sub-directories and files of this Directory. + /// + /// Platforms may throw an exception if there is an error listing entities in + /// the directory + Stream list(PlatformListParams params); +} + +/// Base class for parameters passed to [PlatformXDirectory.list]. +@immutable +base class PlatformListParams { + /// Constructs a [PlatformListParams]; + const PlatformListParams(); +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_entity.dart b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_entity.dart new file mode 100644 index 000000000000..c9613ae0932e --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_entity.dart @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show immutable, protected; + +/// The common superclass for [PlatformXFileCreationParams] and +/// [PlatformXDirectoryCreationParams]. +@immutable +abstract base class PlatformXEntityCreationParams { + /// Constructs a [PlatformXEntityCreationParams]. + const PlatformXEntityCreationParams({required this.uri}); + + /// A unique string used to identify the resource. + final String uri; +} + +/// The common superclass for [PlatformXFileExtension] and +/// [PlatformXDirectoryExtension]. +mixin PlatformXEntityExtension {} + +/// The common superclass for [PlatformXFile] and [PlatformXDirectory]. +abstract base class PlatformXEntity { + /// Constructs a [PlatformXEntity]. + @protected + PlatformXEntity(this.params); + + /// The parameters used to initialize the [PlatformXEntity]. + final PlatformXEntityCreationParams params; + + /// Extension for providing platform-specific features. + PlatformXEntityExtension? get extension => null; + + /// Whether the resource represented by this reference exists. + Future exists(); +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file.dart b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file.dart new file mode 100644 index 000000000000..34c8657b4369 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file.dart @@ -0,0 +1,126 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart' show immutable, protected; + +import 'platform_cross_entity.dart'; + +/// Object specifying creation parameters for creating a [PlatformXFile]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformXFileCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformXFileCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidXFileCreationParams +/// extends PlatformXFileCreationParams { +/// AndroidXFileCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidXFileCreationParams.fromCreationParams( +/// PlatformXFileCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidXFileCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformXFileCreationParams extends PlatformXEntityCreationParams { + /// Constructs a [PlatformXFileCreationParams]. + const PlatformXFileCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformXFile]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformXFile.extension]. +/// +/// ```dart +/// base class AndroidXFile extends PlatformXFile with AndroidXFileExtension { +/// // ... +/// @override +/// PlatformXFileExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidXFileExtension implements PlatformXFileExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformXFileExtension implements PlatformXEntityExtension {} + +/// Interface for a reference to a local data resource. +abstract base class PlatformXFile extends PlatformXEntity { + /// Constructs a [PlatformXFile]. + @protected + PlatformXFile(PlatformXFileCreationParams super.params); + + @override + PlatformXFileCreationParams get params => super.params as PlatformXFileCreationParams; + + /// Extension for providing platform-specific features. + @override + PlatformXFileExtension? get extension => null; + + /// Date and time when the resource was last modified, if the information is + /// available. + /// + /// Returns null if file doesn't exist or information is not available. + Future lastModified(); + + /// The length of the data represented by this uri, in bytes. + /// + /// Returns null if file doesn't exist or information is not available. + Future length(); + + /// Creates a new independent Stream for the contents of this resource. + /// + /// If start is present, the file will be read from byte-offset start. + /// Otherwise from the beginning (index 0). + /// + /// If end is present, only bytes up to byte-index end will be read. + /// Otherwise, until end of file. + /// + /// Platforms may throw an exception if there is an error opening or reading + /// the resource. + Stream openRead([int? start, int? end]); + + /// Reads the entire resource contents as a list of bytes. + /// + /// Platforms may throw an exception if there is an error opening or reading + /// the resource. + Future readAsBytes(); + + /// Reads the entire resource contents as a string using the given Encoding. + /// + /// Platforms may throw an exception if there is an error opening or reading + /// the resource. + Future readAsString({Encoding encoding = utf8}); + + /// The name of the resource represented by this object or null if the file + /// doesn't exist or information is not available. + /// + /// If the file is identified by a path, only the base name of the file will + /// be included in the name. + Future name(); +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_directory.dart b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_directory.dart new file mode 100644 index 000000000000..10accdcbfbb7 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_directory.dart @@ -0,0 +1,109 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import '../cross_file_platform.dart'; +import '../platform_cross_directory.dart'; + +/// Object specifying creation parameters for creating a [PlatformScopedStorageXDirectory]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformScopedStorageXDirectoryCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformScopedStorageXDirectoryCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidScopedStorageXDirectoryCreationParams +/// extends PlatformScopedStorageXDirectoryCreationParams { +/// AndroidScopedStorageXDirectoryCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidScopedStorageXDirectoryCreationParams.fromCreationParams( +/// PlatformScopedStorageXDirectoryCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidScopedStorageXDirectoryCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformScopedStorageXDirectoryCreationParams extends PlatformXDirectoryCreationParams { + /// Constructs a [PlatformScopedStorageXDirectoryCreationParams]. + const PlatformScopedStorageXDirectoryCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformScopedStorageXDirectory]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformScopedStorageXDirectory.extension]. +/// +/// ```dart +/// base class AndroidScopedStorageXDirectory extends PlatformScopedStorageXDirectory with AndroidScopedStorageXDirectoryExtension { +/// // ... +/// @override +/// PlatformScopedStorageXDirectoryExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidScopedStorageXDirectoryExtension implements PlatformScopedStorageXDirectoryExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformScopedStorageXDirectoryExtension implements PlatformXDirectoryExtension {} + +/// Interface for a reference to a directory (or folder) within a device's +/// scoped storage. +abstract base class PlatformScopedStorageXDirectory extends PlatformXDirectory { + /// Creates a new [PlatformScopedStorageXDirectory] + factory PlatformScopedStorageXDirectory(PlatformScopedStorageXDirectoryCreationParams params) { + assert( + CrossFilePlatform.instance != null, + 'A platform implementation for `cross_file` has not been set. Please ' + 'ensure that an implementation of `CrossFilePlatform` has been set to ' + '`CrossFilePlatform.instance` before use. For unit testing, ' + '`CrossFilePlatform.instance` can be set with your own test implementation.', + ); + return CrossFilePlatform.instance!.createPlatformScopedStorageXDirectory(params); + } + + /// Used by the platform implementation to create a new + /// [PlatformScopedStorageXDirectory]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformScopedStorageXDirectory.implementation( + PlatformScopedStorageXDirectoryCreationParams super.params, + ); + + @override + PlatformScopedStorageXDirectoryCreationParams get params => + super.params as PlatformScopedStorageXDirectoryCreationParams; + + /// Extension for providing platform-specific features. + @override + PlatformScopedStorageXDirectoryExtension? get extension => null; + + /// Whether the files in this directory represented by this reference can be + /// viewed. + Future canRead(); + + /// Release the reference this represents. + Future dispose(); +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_file.dart b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_file.dart new file mode 100644 index 000000000000..edab5cfbb566 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_file.dart @@ -0,0 +1,106 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import '../cross_file_platform.dart'; +import '../platform_cross_file.dart'; + +/// Object specifying creation parameters for creating a [PlatformScopedStorageXFile]. +/// +/// Platform-specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the [PlatformScopedStorageXFileCreationParams] to +/// provide additional platform-specific parameters. +/// +/// When extending [PlatformScopedStorageXFileCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// base class AndroidScopedStorageXFileCreationParams +/// extends PlatformScopedStorageXFileCreationParams { +/// AndroidScopedStorageXFileCreationParams({required super.uri, this.platformValue}); +/// +/// factory AndroidScopedStorageXFileCreationParams.fromCreationParams( +/// PlatformScopedStorageXFileCreationParams params, { +/// Object? platformValue, +/// }) { +/// return AndroidScopedStorageXFileCreationParams( +/// uri: params.uri, +/// platformValue: platformValue, +/// ); +/// } +/// +/// final Object? platformValue; +/// } +/// ``` +@immutable +base class PlatformScopedStorageXFileCreationParams extends PlatformXFileCreationParams { + /// Constructs a [PlatformScopedStorageXFileCreationParams]. + const PlatformScopedStorageXFileCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform-specific features for implementations of +/// [PlatformScopedStorageXFile]. +/// +/// When providing platform specific features, platform implementations are +/// expected to declare a mixin that implements this mixin and return an +/// instance with [PlatformScopedStorageXFile.extension]. +/// +/// ```dart +/// base class AndroidScopedStorageXFile extends PlatformScopedStorageXFile with AndroidScopedStorageXFileExtension { +/// // ... +/// @override +/// PlatformScopedStorageXFileExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidScopedStorageXFileExtension implements PlatformScopedStorageXFileExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformScopedStorageXFileExtension implements PlatformXFileExtension {} + +/// Interface for a reference to a data resource within a device's scoped +/// storage. +abstract base class PlatformScopedStorageXFile extends PlatformXFile { + /// Creates a new [PlatformScopedStorageXFile] + factory PlatformScopedStorageXFile(PlatformScopedStorageXFileCreationParams params) { + assert( + CrossFilePlatform.instance != null, + 'A platform implementation for `cross_file` has not been set. Please ' + 'ensure that an implementation of `CrossFilePlatform` has been set to ' + '`CrossFilePlatform.instance` before use. For unit testing, ' + '`CrossFilePlatform.instance` can be set with your own test implementation.', + ); + return CrossFilePlatform.instance!.createPlatformScopedStorageXFile(params); + } + + /// Used by the platform implementation to create a new + /// [PlatformScopedStorageXFile]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformScopedStorageXFile.implementation(PlatformScopedStorageXFileCreationParams super.params); + + /// Extension for providing platform-specific features. + @override + PlatformScopedStorageXFileExtension? get extension => null; + + @override + PlatformScopedStorageXFileCreationParams get params => + super.params as PlatformScopedStorageXFileCreationParams; + + /// Whether the resource represented by this reference can be read. + Future canRead(); + + /// Release the reference this represents. + Future dispose(); +} diff --git a/packages/cross_file/cross_file_platform_interface/pubspec.yaml b/packages/cross_file/cross_file_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..ec9eb9ec277a --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/pubspec.yaml @@ -0,0 +1,23 @@ +name: cross_file_platform_interface +description: A common platform interface for the cross_file plugin. +repository: https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_platform_interface +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 +# 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: 1.0.0 + +environment: + sdk: ^3.10.0 + flutter: ">=3.38.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +topics: + - files + - cross-file diff --git a/packages/cross_file/cross_file_platform_interface/test/cross_file_platform_test.dart b/packages/cross_file/cross_file_platform_interface/test/cross_file_platform_test.dart new file mode 100644 index 000000000000..023b6cdbdcf6 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/test/cross_file_platform_test.dart @@ -0,0 +1,159 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CrossFilePlatform', () { + group('FileSystem', () { + test('PlatformFileSystemXFile returns correct uri', () { + final platform = TestCrossFilePlatform(); + + expect( + platform + .createPlatformFileSystemXFile(PlatformFileSystemXFileCreationParams('my/path.txt')) + .params + .uri, + 'my/path.txt', + ); + + expect( + platform + .createPlatformFileSystemXFile(PlatformFileSystemXFileCreationParams('/my/path.txt')) + .params + .uri, + 'file:///my/path.txt', + ); + }); + + test('PlatformFileSystemXDirectory returns correct URI', () { + final platform = TestCrossFilePlatform(); + + expect( + platform + .createPlatformFileSystemXDirectory( + PlatformFileSystemXDirectoryCreationParams('my/path'), + ) + .params + .uri, + 'my/path/', + ); + + expect( + platform + .createPlatformFileSystemXFile(PlatformFileSystemXFileCreationParams('/my/path/')) + .params + .uri, + 'file:///my/path/', + ); + }); + + test('_DefaultFileSystemXFile.exists() returns false', () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformFileSystemXFile(PlatformFileSystemXFileCreationParams('test')) + .exists(), + false, + ); + }); + + test('_DefaultFileSystemXFile.openRead should throw error by adding it to stream', () async { + final platform = TestCrossFilePlatform(); + + final PlatformFileSystemXFile file = platform.createPlatformFileSystemXFile( + PlatformFileSystemXFileCreationParams('test'), + ); + + // Ensures the error is caught and added to the stream. + await expectLater(file.openRead().drain, throwsUnsupportedError); + }); + + test('_DefaultFileSystemXDirectory.exists() returns false', () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformFileSystemXDirectory( + PlatformFileSystemXDirectoryCreationParams('test'), + ) + .exists(), + false, + ); + }); + + test('_DefaultFileSystemXDirectory.list should throw error by adding it to stream', () async { + final platform = TestCrossFilePlatform(); + + final PlatformFileSystemXDirectory dir = platform.createPlatformFileSystemXDirectory( + PlatformFileSystemXDirectoryCreationParams('test'), + ); + + // Ensures the error is caught and added to the stream. + await expectLater(dir.list(const PlatformListParams()).drain, throwsUnsupportedError); + }); + }); + + group('ScopedStorage', () { + test('_DefaultScopedStorageXFile.exists() returns false', () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: 'test'), + ) + .exists(), + false, + ); + }); + + test( + '_DefaultScopedStorageXFile.openRead should throw error by adding it to stream', + () async { + final platform = TestCrossFilePlatform(); + + final PlatformScopedStorageXFile file = platform.createPlatformScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: 'test'), + ); + + // Ensures the error is caught and added to the stream. + await expectLater(file.openRead().drain, throwsUnsupportedError); + }, + ); + + test('_DefaultScopedStorageXDirectory.exists() returns false', () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams(uri: 'test'), + ) + .exists(), + false, + ); + }); + + test( + '_DefaultScopedStorageXDirectory.list should throw error by adding it to stream', + () async { + final platform = TestCrossFilePlatform(); + + final PlatformScopedStorageXDirectory dir = platform + .createPlatformScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams(uri: 'test'), + ); + + // Ensures the error is caught and added to the stream. + await expectLater(dir.list(const PlatformListParams()).drain, throwsUnsupportedError); + }, + ); + }); + }); +} + +final class TestCrossFilePlatform extends CrossFilePlatform {}