diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9b108e970a78..0ae1ab5e3de6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,6 +29,9 @@ updates: - /packages/camera/camera_android/example/android/app - /packages/camera/camera_android_camerax/example/android/app - /packages/camera/camera/example/android/app + - /packages/cross_file/cross_file/example/android/app + - /packages/cross_file/cross_file_android/android + - /packages/cross_file/cross_file_android/example/android/app - /packages/espresso/example/android/app - /packages/extension_google_sign_in_as_googleapis_auth/example/android/app - /packages/file_selector/file_selector/example/android/app diff --git a/CODEOWNERS b/CODEOWNERS index c3c019649b1d..3a6298803577 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,7 +6,8 @@ packages/animations/** @hannah-hyj packages/camera/** @bparrishMines -packages/cross_file/** @stuartmorgan-g +packages/cross_file/** @bparrishMines +packages/cross_file/cross_file_io/** @bparrishMines packages/extension_google_sign_in_as_googleapis_auth/** @stuartmorgan-g packages/file_selector/** @stuartmorgan-g packages/flutter_lints/** @chunhtai @@ -54,6 +55,7 @@ third_party/packages/path_parsing/** @domesticmouse # - Web packages/camera/camera_web/** @mdebbar +packages/cross_file/cross_file_web/** @bparrishMines packages/file_selector/file_selector_web/** @mdebbar packages/google_maps_flutter/google_maps_flutter_web/** @mdebbar packages/google_sign_in/google_sign_in_web/** @mdebbar @@ -67,6 +69,7 @@ packages/webview_flutter/webview_flutter_web/** @mdebbar # - Android packages/camera/camera_android/** @camsim99 packages/camera/camera_android_camerax/** @camsim99 +packages/cross_file/cross_file_android/** @bparrishMines packages/espresso/** @jesswrd packages/file_selector/file_selector_android/** @mboetger packages/flutter_plugin_android_lifecycle/** @reidbaker @@ -85,6 +88,7 @@ packages/webview_flutter/webview_flutter_android/** @bparrishMines # - Darwin packages/camera/camera_avfoundation/** @hellohuanlin @louisehsu +packages/cross_file/cross_file_darwin/** @bparrishMines packages/file_selector/file_selector_ios/** @okorohelijah @vashworth packages/file_selector/file_selector_macos/** @okorohelijah @vashworth packages/google_maps_flutter/google_maps_flutter_ios/** @vashworth @LongCatIsLooong diff --git a/packages/cross_file/README.md b/packages/cross_file/README.md deleted file mode 100644 index 290295571877..000000000000 --- a/packages/cross_file/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# cross_file - -An abstraction to allow working with files across multiple platforms. - -## Usage - -Import `package:cross_file/cross_file.dart`, instantiate a `XFile` -using a path or byte array and use its methods and properties to -access the file and its metadata. - -Example: - - -```dart -final file = XFile('assets/hello.txt'); - -print('File information:'); -print('- Path: ${file.path}'); -print('- Name: ${file.name}'); -print('- MIME type: ${file.mimeType}'); - -final String fileContent = await file.readAsString(); -print('Content of the file: $fileContent'); -``` - -You will find links to the API docs on the [pub page](https://pub.dev/packages/cross_file). - -## Web Limitations - -`XFile` on the web platform is backed by [Blob](https://api.dart.dev/be/180361/dart-html/Blob-class.html) -objects and their URLs. - -It seems that Safari hangs when reading Blobs larger than 4GB (your app will stop -without returning any data, or throwing an exception). - -This package will attempt to throw an `Exception` before a large file is accessed -from Safari (if its size is known beforehand), so that case can be handled -programmatically. - -### Browser compatibility - -[![Data on Global support for Blob constructing](https://caniuse.bitsofco.de/image/blobbuilder.png)](https://caniuse.com/blobbuilder) - -[![Data on Global support for Blob URLs](https://caniuse.bitsofco.de/image/bloburls.png)](https://caniuse.com/bloburls) - -## Testing - -This package supports both web and native platforms. Unit tests need to be split -in two separate suites (because native code cannot use `package:web`, and web code -cannot use `dart:io`). - -When adding new features, it is likely that tests need to be added for both the -native and web platforms. - -### Native tests - -Tests for native platforms are located in the `x_file_io_test.dart`. Tests can -be run with `dart test`. - -### Web tests - -Tests for the web platform live in the `x_file_html_test.dart`. They can be run -with `dart test -p chrome`. diff --git a/packages/cross_file/AUTHORS b/packages/cross_file/cross_file/AUTHORS similarity index 100% rename from packages/cross_file/AUTHORS rename to packages/cross_file/cross_file/AUTHORS diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/cross_file/CHANGELOG.md similarity index 97% rename from packages/cross_file/CHANGELOG.md rename to packages/cross_file/cross_file/CHANGELOG.md index 73e9db3048af..19a460dd2963 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/cross_file/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0 + +* Updates `cross_file` to a package separated federated plugin. + ## 0.3.5+2 * Separates "Save As" implementation details from XFile web class. diff --git a/packages/cross_file/LICENSE b/packages/cross_file/cross_file/LICENSE similarity index 100% rename from packages/cross_file/LICENSE rename to packages/cross_file/cross_file/LICENSE diff --git a/packages/cross_file/cross_file/README.md b/packages/cross_file/cross_file/README.md new file mode 100644 index 000000000000..096a11e94733 --- /dev/null +++ b/packages/cross_file/cross_file/README.md @@ -0,0 +1,112 @@ +# cross_file + +An abstraction to allow working with files across multiple platforms. + + + +[![pub package](https://img.shields.io/pub/v/cross_file.svg)](https://pub.dartlang.org/packages/cross_file) + +A Flutter plugin that manages files and interactions with file dialogs. + +| | Android | iOS | Linux | macOS | Web | Windows | +|-------------|---------|---------|-------|--------|-----|-------------| +| **Support** | SDK 24+ | iOS 13+ | Any | 10.15+ | Any | Windows 10+ | + +## Usage + +Import `package:cross_file/cross_file.dart`, instantiate a `XFile` +using a path or byte array and use its methods and properties to +access the file and its metadata. + +Example: + + +```dart +final file = XFile.fromUri(Uri.file('assets/hello.txt')); + +debugPrint('File information:'); +debugPrint('- URI: ${file.uri}'); +debugPrint('- Name: ${await file.name()}'); + +if (await file.canRead()) { + final String fileContent = await file.readAsString(); + debugPrint('Content of the file: $fileContent'); +} +``` + +You will find links to the API docs on the [pub page](https://pub.dev/packages/cross_file). + +### Implementation-Specific Features + +Classes in this package contain an underlying platform implementation that provides features that +are specific to an implementation. + +To access implementation-specific features, start by adding the platform implementation packages to +your app or package: + +* **dart:io** [cross_file_io](https://pub.dev/packages/cross_file_io/install) +* **Android Scoped Storage**: [cross_file_android](https://pub.dev/packages/cross_file_android/install) +* **iOS/macOS App Sandbox**: [cross_file_darwin](https://pub.dev/packages/cross_file_darwin/install) +* **Web**: [cross_file_web](https://pub.dev/packages/cross_file_web/install) + +Next, add the imports of the implementation packages to your app or package: + + +```dart +// Import for Darwin App Sandbox features. +import 'package:cross_file_darwin/cross_file_darwin.dart'; +// Import for Web features. +import 'package:cross_file_web/cross_file_web.dart'; +``` + +Now, additional features can be accessed through the platform implementations. Classes +[XFile], [XDirectory], [ScopedStorageXFile], and [ScopedStorageXDirectory] pass their +functionality to a class provided by the current platform. Below are a couple of ways to access +additional functionality provided by the platform and is followed by an example. + +1. Pass a creation params class provided by a platform implementation to a `fromCreationParams` + constructor (e.g. `XFile.fromCreationParams`, `XDirectory.fromCreationParams`, etc.). +2. Call methods on an implementation of a class by using `getExtension`/`maybeGetExtension` methods (e.g. + `XFile.getExtension`, `XDirectory.maybeGetExtension`, etc.). + +Below is an example of setting additional iOS/macOS and Android parameters on a `XFile`. + + +```dart +late final XFile file; + +if (CrossFilePlatform.instance is CrossFileWeb) { + final params = WebXFileCreationParams.fromObjectUrl( + objectUrl: 'blob:https://some/url:for/file', + ); + file = XFile.fromCreationParams(params); +} else if (CrossFilePlatform.instance is CrossFileDarwin) { + final params = PlatformScopedStorageXFileCreationParams( + uri: Uri.file('/my/file.txt').toString(), + ); + file = ScopedStorageXFile.fromCreationParams(params); + + await file + .getExtension() + .startAccessingSecurityScopedResource(); +} else { + file = XFile.fromUri(Uri.file('/my/file.txt')); +} + +debugPrint(await file.readAsString()); +await file + .maybeGetExtension() + ?.stopAccessingSecurityScopedResource(); +``` + +See https://pub.dev/documentation/cross_file_darwin/latest/cross_file_darwin/cross_file_darwin-library.html +for more details on iOS/macOS App Sandbox features. + +See https://pub.dev/documentation/cross_file_android/latest/cross_file_android/cross_file_android-library.html +for more details on Android Scoped Storage features. + +See https://pub.dev/documentation/cross_file_io/latest/cross_file_io/cross_file_io-library.html +for more details on `dart:io` features. + +See https://pub.dev/documentation/cross_file_web/latest/cross_file_web/cross_file_web-library.html +for more details on Web features. diff --git a/packages/cross_file/cross_file/example/README.md b/packages/cross_file/cross_file/example/README.md new file mode 100644 index 000000000000..96b8bb17dbff --- /dev/null +++ b/packages/cross_file/cross_file/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/packages/cross_file/cross_file/example/android/app/build.gradle.kts b/packages/cross_file/cross_file/example/android/app/build.gradle.kts new file mode 100644 index 000000000000..adc6a98c5171 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "dev.flutter.packages.cross_file_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "dev.flutter.packages.cross_file_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/cross_file/cross_file/example/android/app/src/debug/AndroidManifest.xml b/packages/cross_file/cross_file/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000000..399f6981d5d3 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/main/AndroidManifest.xml b/packages/cross_file/cross_file/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..5616b0f08984 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_example/MainActivity.kt b/packages/cross_file/cross_file/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_example/MainActivity.kt new file mode 100644 index 000000000000..8ef9b9f2e1d8 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_example/MainActivity.kt @@ -0,0 +1,9 @@ +// 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. + +package dev.flutter.packages.cross_file_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/cross_file/cross_file/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000000..f74085f3f6a2 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/drawable/launch_background.xml b/packages/cross_file/cross_file/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..09d4391482be Binary files /dev/null and b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/cross_file/cross_file/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/values-night/styles.xml b/packages/cross_file/cross_file/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000000..06952be745f9 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/main/res/values/styles.xml b/packages/cross_file/cross_file/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..cb1ef88056ed --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/cross_file/cross_file/example/android/app/src/profile/AndroidManifest.xml b/packages/cross_file/cross_file/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000000..399f6981d5d3 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/cross_file/cross_file/example/android/build.gradle b/packages/cross_file/cross_file/example/android/build.gradle new file mode 100644 index 000000000000..b9db5700753a --- /dev/null +++ b/packages/cross_file/cross_file/example/android/build.gradle @@ -0,0 +1,24 @@ +allprojects { + repositories { + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. + def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' + if (System.getenv().containsKey(artifactRepoKey)) { + println "Using artifact hub" + maven { url System.getenv(artifactRepoKey) } + } + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/cross_file/cross_file/example/android/gradle.properties b/packages/cross_file/cross_file/example/android/gradle.properties new file mode 100644 index 000000000000..fbee1d8cdafc --- /dev/null +++ b/packages/cross_file/cross_file/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/packages/cross_file/cross_file/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/cross_file/cross_file/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..e4ef43fb98df --- /dev/null +++ b/packages/cross_file/cross_file/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/packages/cross_file/cross_file/example/android/settings.gradle.kts b/packages/cross_file/cross_file/example/android/settings.gradle.kts new file mode 100644 index 000000000000..ca7fe065c167 --- /dev/null +++ b/packages/cross_file/cross_file/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/packages/cross_file/example/assets/hello.txt b/packages/cross_file/cross_file/example/assets/hello.txt similarity index 100% rename from packages/cross_file/example/assets/hello.txt rename to packages/cross_file/cross_file/example/assets/hello.txt diff --git a/packages/cross_file/cross_file/example/ios/Flutter/AppFrameworkInfo.plist b/packages/cross_file/cross_file/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000000..391a902b2beb --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/packages/cross_file/cross_file/example/ios/Flutter/Debug.xcconfig b/packages/cross_file/cross_file/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..ec97fc6f3021 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/cross_file/cross_file/example/ios/Flutter/Release.xcconfig b/packages/cross_file/cross_file/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..c4855bfe2000 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/cross_file/cross_file/example/ios/Podfile b/packages/cross_file/cross_file/example/ios/Podfile new file mode 100644 index 000000000000..620e46eba607 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.pbxproj b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..c6bc1f30ab8d --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,726 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 9D5ABF2BFD3F5F3216E37BDF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 504968999090CEE6BD5E6AC4 /* Pods_Runner.framework */; }; + C7741C8FE1202DC4D2942D67 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F576F2A577117482CBAA056 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4C3E3EE98AFDD7AD2C241987 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 4F576F2A577117482CBAA056 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 504968999090CEE6BD5E6AC4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 725B8D3BB6CDC5144FD4E5B7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A008FDEFE186795A9B02CB4F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A5578CFEE2C8B362F29ECF39 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + DB861370BF100AD4A286A356 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + FC3B6166344B1B39C67DF648 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D5ABF2BFD3F5F3216E37BDF /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E42B53E4B6191CE70EE6A051 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C7741C8FE1202DC4D2942D67 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + C6869AEB60FAA8D6CEE6FA87 /* Pods */, + 9F5AF9B51C64E38CA0A7E19F /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 9F5AF9B51C64E38CA0A7E19F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 504968999090CEE6BD5E6AC4 /* Pods_Runner.framework */, + 4F576F2A577117482CBAA056 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C6869AEB60FAA8D6CEE6FA87 /* Pods */ = { + isa = PBXGroup; + children = ( + 725B8D3BB6CDC5144FD4E5B7 /* Pods-Runner.debug.xcconfig */, + A008FDEFE186795A9B02CB4F /* Pods-Runner.release.xcconfig */, + A5578CFEE2C8B362F29ECF39 /* Pods-Runner.profile.xcconfig */, + DB861370BF100AD4A286A356 /* Pods-RunnerTests.debug.xcconfig */, + 4C3E3EE98AFDD7AD2C241987 /* Pods-RunnerTests.release.xcconfig */, + FC3B6166344B1B39C67DF648 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 7D383C8584CA0BAB73229DC3 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + E42B53E4B6191CE70EE6A051 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 81428E1333C25AFC5945A7F4 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 9050369081B08E0B437C3785 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 7D383C8584CA0BAB73229DC3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 81428E1333C25AFC5945A7F4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9050369081B08E0B437C3785 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DB861370BF100AD4A286A356 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4C3E3EE98AFDD7AD2C241987 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FC3B6166344B1B39C67DF648 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..919434a6254f --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..e3773d42e24c --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner/AppDelegate.swift b/packages/cross_file/cross_file/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000000..81eca8683601 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,20 @@ +// 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 Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..d36b1fab2d9d --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000000..dc9ada4725e9 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000000..7353c41ecf9c Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000000..797d452e4589 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000000..6ed2d933e112 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000000..4cd7b0099ca8 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000000..fe730945a01f Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000000..321773cd857a Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000000..797d452e4589 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000000..502f463a9bc8 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000000..0ec303439225 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000000..0ec303439225 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000000..e9f5fea27c70 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000000..84ac32ae7d98 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000000..8953cba09064 Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000000..0467bf12aa4d Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000..f2e259c7c939 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/Main.storyboard b/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/ios/Runner/Info.plist b/packages/cross_file/cross_file/example/ios/Runner/Info.plist new file mode 100644 index 000000000000..d512223bafe5 --- /dev/null +++ b/packages/cross_file/cross_file/example/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Cross File Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + cross_file_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/packages/cross_file/lib/cross_file.dart b/packages/cross_file/cross_file/example/ios/Runner/Runner-Bridging-Header.h similarity index 79% rename from packages/cross_file/lib/cross_file.dart rename to packages/cross_file/cross_file/example/ios/Runner/Runner-Bridging-Header.h index f0fc41b9919e..ba04211afd0a 100644 --- a/packages/cross_file/lib/cross_file.dart +++ b/packages/cross_file/cross_file/example/ios/Runner/Runner-Bridging-Header.h @@ -2,4 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'src/x_file.dart'; +#import "GeneratedPluginRegistrant.h" diff --git a/packages/cross_file/lib/src/x_file.dart b/packages/cross_file/cross_file/example/ios/Runner/SceneDelegate.swift similarity index 53% rename from packages/cross_file/lib/src/x_file.dart rename to packages/cross_file/cross_file/example/ios/Runner/SceneDelegate.swift index 0d00d96fd1a3..8c7b10c639d1 100644 --- a/packages/cross_file/lib/src/x_file.dart +++ b/packages/cross_file/cross_file/example/ios/Runner/SceneDelegate.swift @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'types/interface.dart' - if (dart.library.js_interop) 'types/html.dart' - if (dart.library.io) 'types/io.dart'; +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/packages/cross_file/cross_file/example/lib/main.dart b/packages/cross_file/cross_file/example/lib/main.dart new file mode 100644 index 000000000000..29b1369ba4da --- /dev/null +++ b/packages/cross_file/cross_file/example/lib/main.dart @@ -0,0 +1,133 @@ +// 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:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:mime/mime.dart' as mime; + +void main() { + runApp(const MaterialApp(home: FileOpenScreen())); +} + +/// Example screen to open a file selector and display it. +class FileOpenScreen extends StatelessWidget { + /// Constructs a [FileOpenScreen]. + const FileOpenScreen({super.key}); + + Future _openFile(BuildContext context) async { + final XFile? file = await openFile(); + + if (file != null) { + final String filename = await file.name() ?? file.uri; + + switch (mime.lookupMimeType(filename)) { + case final String mimeType when mimeType.startsWith('text'): + final String fileContents = await file.readAsString(); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(filename: filename, fileContents: fileContents), + ); + } + case _: + debugPrint('File Uri: ${file.uri}'); + debugPrint('Filename: $filename'); + debugPrint('Can Read File: ${await file.canRead()}'); + debugPrint('File Length: ${await file.length()}'); + debugPrint('File Last Modified: ${await file.lastModified()}'); + return; + } + } else { + debugPrint('No file selected.'); + } + } + + Future _openDirectory() async { + final XDirectory? directory = await getDirectoryPath(); + + if (directory != null) { + debugPrint('Directory Uri: ${directory.uri}'); + debugPrint('Directory exists: ${await directory.exists()}'); + + debugPrint('List of Entities:'); + await for (final XFileEntity entity in directory.list()) { + switch (entity) { + case final XFile file: + final String filename = await file.name() ?? file.uri; + debugPrint('\tFile: $filename'); + case final XDirectory directory: + debugPrint('\tDirectory: ${directory.uri}'); + } + } + } else { + debugPrint('No directory selected.'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Open a File'), + backgroundColor: Colors.blue, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open File'), + onPressed: () => _openFile(context), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open Directory'), + onPressed: () => _openDirectory(), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Default Constructor. + const TextDisplay({ + super.key, + required this.filename, + required this.fileContents, + }); + + /// The name of the file. + final String filename; + + /// The contents of the file. + final String fileContents; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(filename), + content: Scrollbar( + child: SingleChildScrollView(child: Text(fileContents)), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/cross_file/cross_file/example/lib/readme_excerpts.dart b/packages/cross_file/cross_file/example/lib/readme_excerpts.dart new file mode 100644 index 000000000000..368e85d256fb --- /dev/null +++ b/packages/cross_file/cross_file/example/lib/readme_excerpts.dart @@ -0,0 +1,62 @@ +// 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/cross_file.dart'; +// #docregion platform_imports +// Import for Darwin App Sandbox features. +import 'package:cross_file_darwin/cross_file_darwin.dart'; +// Import for Web features. +import 'package:cross_file_web/cross_file_web.dart'; +// #enddocregion platform_imports +import 'package:flutter/foundation.dart' show debugPrint; + +/// Demonstrate instantiating an XFile for the README. +Future instantiateXFile() async { + // #docregion Instantiate + final file = XFile.fromUri(Uri.file('assets/hello.txt')); + + debugPrint('File information:'); + debugPrint('- URI: ${file.uri}'); + debugPrint('- Name: ${await file.name()}'); + + if (await file.canRead()) { + final String fileContent = await file.readAsString(); + debugPrint('Content of the file: $fileContent'); + } + // #enddocregion Instantiate + + return file; +} + +/// Demonstrate accessing platform features. +Future accessPlatformFeatures() async { + // #docregion platform_features + late final XFile file; + + if (CrossFilePlatform.instance is CrossFileWeb) { + final params = WebXFileCreationParams.fromObjectUrl( + objectUrl: 'blob:https://some/url:for/file', + ); + file = XFile.fromCreationParams(params); + } else if (CrossFilePlatform.instance is CrossFileDarwin) { + final params = PlatformScopedStorageXFileCreationParams( + uri: Uri.file('/my/file.txt').toString(), + ); + file = ScopedStorageXFile.fromCreationParams(params); + + await file + .getExtension() + .startAccessingSecurityScopedResource(); + } else { + file = XFile.fromUri(Uri.file('/my/file.txt')); + } + + debugPrint(await file.readAsString()); + await file + .maybeGetExtension() + ?.stopAccessingSecurityScopedResource(); + // #enddocregion platform_features + + return file; +} diff --git a/packages/cross_file/cross_file/example/linux/CMakeLists.txt b/packages/cross_file/cross_file/example/linux/CMakeLists.txt new file mode 100644 index 000000000000..ed0f020b39f0 --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "cross_file_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutter.packages.cross_file_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/cross_file/cross_file/example/linux/flutter/CMakeLists.txt b/packages/cross_file/cross_file/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000000..d5bd01648a96 --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/cross_file/cross_file/example/linux/flutter/generated_plugins.cmake b/packages/cross_file/cross_file/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..2e1de87a7eb6 --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/cross_file/cross_file/example/linux/runner/CMakeLists.txt b/packages/cross_file/cross_file/example/linux/runner/CMakeLists.txt new file mode 100644 index 000000000000..e97dabc7028e --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/packages/cross_file/cross_file/example/linux/runner/main.cc b/packages/cross_file/cross_file/example/linux/runner/main.cc new file mode 100644 index 000000000000..3b03bbf6941f --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/runner/main.cc @@ -0,0 +1,10 @@ +// 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. + +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/cross_file/cross_file/example/linux/runner/my_application.cc b/packages/cross_file/cross_file/example/linux/runner/my_application.cc new file mode 100644 index 000000000000..2ca8f980ed14 --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/runner/my_application.cc @@ -0,0 +1,152 @@ +// 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. + +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "cross_file_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "cross_file_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/packages/cross_file/cross_file/example/linux/runner/my_application.h b/packages/cross_file/cross_file/example/linux/runner/my_application.h new file mode 100644 index 000000000000..9ae704a9f417 --- /dev/null +++ b/packages/cross_file/cross_file/example/linux/runner/my_application.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000000..4b81f9b2d200 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Release.xcconfig b/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000000..5caa9d1579e4 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/cross_file/cross_file/example/macos/Podfile b/packages/cross_file/cross_file/example/macos/Podfile new file mode 100644 index 000000000000..ff5ddb3b8bdc --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.pbxproj b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..3527df952cce --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,792 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 223056F6078A0B29F9794331 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13AF930B762038462526C125 /* Pods_RunnerTests.framework */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + CDCABB3C5E47BFDA2D2D1F97 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD409983E262F02D75B6545E /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 13AF930B762038462526C125 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1791B30977A096C697D5C0C4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* cross_file_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cross_file_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 437F33A43BFDC4B887DE8BDA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 51890A064A97BBBEC5098087 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A8EF8F3FBFE0FFF37E4022DC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AC12149C65C95C1AE1180FD1 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + B63E82A3F401053BCA32C3C4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + FD409983E262F02D75B6545E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 223056F6078A0B29F9794331 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDCABB3C5E47BFDA2D2D1F97 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 3DE641040F107AF7D132DC0F /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* cross_file_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 3DE641040F107AF7D132DC0F /* Pods */ = { + isa = PBXGroup; + children = ( + B63E82A3F401053BCA32C3C4 /* Pods-Runner.debug.xcconfig */, + 51890A064A97BBBEC5098087 /* Pods-Runner.release.xcconfig */, + 1791B30977A096C697D5C0C4 /* Pods-Runner.profile.xcconfig */, + 437F33A43BFDC4B887DE8BDA /* Pods-RunnerTests.debug.xcconfig */, + A8EF8F3FBFE0FFF37E4022DC /* Pods-RunnerTests.release.xcconfig */, + AC12149C65C95C1AE1180FD1 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FD409983E262F02D75B6545E /* Pods_Runner.framework */, + 13AF930B762038462526C125 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + C46F10307CFD10DE28500907 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 254EC0B0D434ADEDBB256DFD /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 83C208260E55B8C426E85FC5 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* cross_file_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 254EC0B0D434ADEDBB256DFD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 83C208260E55B8C426E85FC5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C46F10307CFD10DE28500907 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 437F33A43BFDC4B887DE8BDA /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A8EF8F3FBFE0FFF37E4022DC /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AC12149C65C95C1AE1180FD1 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..cca2d5dd0577 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner/AppDelegate.swift b/packages/cross_file/cross_file/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000000..88aaa2ff58ac --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// 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 Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..a2ec33f19f11 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000000..82b6f9d9a33e Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000000..13b35eba55c6 Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000000..0a3f5fa40fb3 Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000000..bdb57226d5f2 Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000000..f083318e09ca Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000000..326c0e72c9d8 Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000000..2f1632cfddf3 Binary files /dev/null and b/packages/cross_file/cross_file/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/cross_file/cross_file/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/cross_file/cross_file/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000000..80e867a4e06b --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/cross_file/cross_file/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000000..e7adb11c6198 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = cross_file_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 dev.flutter.packages. All rights reserved. diff --git a/packages/cross_file/cross_file/example/macos/Runner/Configs/Debug.xcconfig b/packages/cross_file/cross_file/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000000..36b0fd9464f4 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/cross_file/cross_file/example/macos/Runner/Configs/Release.xcconfig b/packages/cross_file/cross_file/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000000..dff4f49561c8 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/cross_file/cross_file/example/macos/Runner/Configs/Warnings.xcconfig b/packages/cross_file/cross_file/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000000..42bcbf4780b1 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/cross_file/cross_file/example/macos/Runner/DebugProfile.entitlements b/packages/cross_file/cross_file/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000000..68acb49b6928 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.files.user-selected.read-write + + + diff --git a/packages/cross_file/cross_file/example/macos/Runner/Info.plist b/packages/cross_file/cross_file/example/macos/Runner/Info.plist new file mode 100644 index 000000000000..4789daa6a443 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/cross_file/cross_file/example/macos/Runner/MainFlutterWindow.swift b/packages/cross_file/cross_file/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000000..df2443c8ebba --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// 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 Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/cross_file/cross_file/example/macos/Runner/Release.entitlements b/packages/cross_file/cross_file/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000000..012d091fca66 --- /dev/null +++ b/packages/cross_file/cross_file/example/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + + diff --git a/packages/cross_file/cross_file/example/pubspec.yaml b/packages/cross_file/cross_file/example/pubspec.yaml new file mode 100644 index 000000000000..d62685cd4438 --- /dev/null +++ b/packages/cross_file/cross_file/example/pubspec.yaml @@ -0,0 +1,59 @@ +name: cross_file_example +description: Demonstrates how to use cross files. +publish_to: none + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +dependencies: + cross_file: + # When depending on this package from a real application you should use: + # cross_file: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file + cross_file_darwin: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_darwin + cross_file_web: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_web + file_selector: + git: + url: https://github.com/bparrishMines/packages + ref: new_file_selector + path: packages/file_selector/file_selector + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + mime: ^2.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/hello.txt + +dependency_overrides: + cross_file: + # When depending on this package from a real application you should use: + # cross_file: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ \ No newline at end of file diff --git a/packages/cross_file/cross_file/example/web/favicon.png b/packages/cross_file/cross_file/example/web/favicon.png new file mode 100644 index 000000000000..8aaa46ac1ae2 Binary files /dev/null and b/packages/cross_file/cross_file/example/web/favicon.png differ diff --git a/packages/cross_file/cross_file/example/web/icons/Icon-192.png b/packages/cross_file/cross_file/example/web/icons/Icon-192.png new file mode 100644 index 000000000000..b749bfef0747 Binary files /dev/null and b/packages/cross_file/cross_file/example/web/icons/Icon-192.png differ diff --git a/packages/cross_file/cross_file/example/web/icons/Icon-512.png b/packages/cross_file/cross_file/example/web/icons/Icon-512.png new file mode 100644 index 000000000000..88cfd48dff11 Binary files /dev/null and b/packages/cross_file/cross_file/example/web/icons/Icon-512.png differ diff --git a/packages/cross_file/cross_file/example/web/icons/Icon-maskable-192.png b/packages/cross_file/cross_file/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000000..eb9b4d76e525 Binary files /dev/null and b/packages/cross_file/cross_file/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/cross_file/cross_file/example/web/icons/Icon-maskable-512.png b/packages/cross_file/cross_file/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000000..d69c56691fbd Binary files /dev/null and b/packages/cross_file/cross_file/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/cross_file/cross_file/example/web/index.html b/packages/cross_file/cross_file/example/web/index.html new file mode 100644 index 000000000000..8030aad1b67f --- /dev/null +++ b/packages/cross_file/cross_file/example/web/index.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + cross_file_example + + + + + + + diff --git a/packages/cross_file/cross_file/example/web/manifest.json b/packages/cross_file/cross_file/example/web/manifest.json new file mode 100644 index 000000000000..fc8c8efa0ca4 --- /dev/null +++ b/packages/cross_file/cross_file/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "cross_file_example", + "short_name": "cross_file_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/cross_file/cross_file/example/windows/CMakeLists.txt b/packages/cross_file/cross_file/example/windows/CMakeLists.txt new file mode 100644 index 000000000000..182e405d908b --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(cross_file_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "cross_file_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/cross_file/cross_file/example/windows/flutter/CMakeLists.txt b/packages/cross_file/cross_file/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000000..903f4899d6fc --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/cross_file/cross_file/example/windows/flutter/generated_plugins.cmake b/packages/cross_file/cross_file/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..b93c4c30c167 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/cross_file/cross_file/example/windows/runner/CMakeLists.txt b/packages/cross_file/cross_file/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000000..394917c053a0 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/cross_file/cross_file/example/windows/runner/Runner.rc b/packages/cross_file/cross_file/example/windows/runner/Runner.rc new file mode 100644 index 000000000000..4396895be5b3 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutter.packages" "\0" + VALUE "FileDescription", "cross_file_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "cross_file_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 dev.flutter.packages. All rights reserved." "\0" + VALUE "OriginalFilename", "cross_file_example.exe" "\0" + VALUE "ProductName", "cross_file_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/cross_file/cross_file/example/windows/runner/flutter_window.cpp b/packages/cross_file/cross_file/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000000..d765fd5d5ef1 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/flutter_window.cpp @@ -0,0 +1,73 @@ +// 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. + +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/cross_file/cross_file/example/windows/runner/flutter_window.h b/packages/cross_file/cross_file/example/windows/runner/flutter_window.h new file mode 100644 index 000000000000..2ef41dd2f67a --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/flutter_window.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/cross_file/cross_file/example/windows/runner/main.cpp b/packages/cross_file/cross_file/example/windows/runner/main.cpp new file mode 100644 index 000000000000..4de872dbe5ee --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/main.cpp @@ -0,0 +1,46 @@ +// 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. + +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t* command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"cross_file_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/cross_file/cross_file/example/windows/runner/resource.h b/packages/cross_file/cross_file/example/windows/runner/resource.h new file mode 100644 index 000000000000..d5d958dc4257 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/cross_file/cross_file/example/windows/runner/resources/app_icon.ico b/packages/cross_file/cross_file/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000000..c04e20caf637 Binary files /dev/null and b/packages/cross_file/cross_file/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/cross_file/cross_file/example/windows/runner/runner.exe.manifest b/packages/cross_file/cross_file/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000000..153653e8d67f --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/packages/cross_file/cross_file/example/windows/runner/utils.cpp b/packages/cross_file/cross_file/example/windows/runner/utils.cpp new file mode 100644 index 000000000000..9db5336111ab --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +// 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. + +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE* unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + nullptr, 0, nullptr, nullptr) - + 1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/cross_file/cross_file/example/windows/runner/utils.h b/packages/cross_file/cross_file/example/windows/runner/utils.h new file mode 100644 index 000000000000..24279736b321 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/utils.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/cross_file/cross_file/example/windows/runner/win32_window.cpp b/packages/cross_file/cross_file/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000000..6ee06d751984 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/win32_window.cpp @@ -0,0 +1,284 @@ +// 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. + +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: +/// https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = + L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { ++g_active_window_count; } + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { return window_handle_; } + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = + RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, + &light_mode, &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/cross_file/cross_file/example/windows/runner/win32_window.h b/packages/cross_file/cross_file/example/windows/runner/win32_window.h new file mode 100644 index 000000000000..1b5ff6023fb2 --- /dev/null +++ b/packages/cross_file/cross_file/example/windows/runner/win32_window.h @@ -0,0 +1,104 @@ +// 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. + +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/cross_file/cross_file/lib/cross_file.dart b/packages/cross_file/cross_file/lib/cross_file.dart new file mode 100644 index 000000000000..afda4f9eb36b --- /dev/null +++ b/packages/cross_file/cross_file/lib/cross_file.dart @@ -0,0 +1,9 @@ +// 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_directory.dart'; +export 'src/cross_file.dart'; +export 'src/cross_file_entity.dart'; +export 'src/scoped_storage_cross_directory.dart'; +export 'src/scoped_storage_cross_file.dart'; diff --git a/packages/cross_file/cross_file/lib/src/cross_directory.dart b/packages/cross_file/cross_file/lib/src/cross_directory.dart new file mode 100644 index 000000000000..636b7f2d781c --- /dev/null +++ b/packages/cross_file/cross_file/lib/src/cross_directory.dart @@ -0,0 +1,114 @@ +// 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/foundation.dart'; + +import 'cross_file.dart'; +import 'cross_file_entity.dart'; +import 'scoped_storage_cross_directory.dart'; +import 'scoped_storage_cross_file.dart'; + +/// A reference to a directory (or folder) on the file system. +/// +/// Note: Not all platforms support directories. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro cross_file.XDirectory.fromCreationParams} +/// +/// Below is an example of accessing the platform-specific extension for +/// the dart:io implementation of `cross_file`: +/// +/// ```dart +/// final XDirectory dir = XDirectory.fromUri(Uri.directory('/my/docs/.')); +/// +/// final IOXDirectoryExtension? ioExtension = file.maybeGetExtension(); +/// if (ioExtension != null) { +/// print(ioExtension.directory.path); +/// } +/// ``` +@immutable +class XDirectory extends XFileEntity { + /// Constructs a [XDirectory]. + /// + /// See [XDirectory.fromCreationParams] for setting parameters for a specific + /// platform. + XDirectory({required String uri}) + : this.fromCreationParams(PlatformXDirectoryCreationParams(uri: uri)); + + /// Constructs a [XDirectory]. + /// + /// See [XDirectory.fromCreationParams] for setting parameters for a specific + /// platform. + XDirectory.fromUri(Uri uri) : this(uri: uri.toString()); + + /// Constructs a [XDirectory] from creation params for a specific platform. + /// + /// {@template cross_file.XDirectory.fromCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// the dart:io implementation of `cross_file`: + /// + /// ```dart + /// var params = const PlatformXDirectoryCreationParams(uri: 'file:///my/docs/'); + /// + /// if (CrossFilePlatform.instance is CrossFileIO) { + /// params = IOXDirectoryCreationParams.fromCreationParams( + /// params, + /// ); + /// } + /// + /// final dir = XDirectory.fromCreationParams(params); + /// ``` + /// {@endtemplate} + XDirectory.fromCreationParams(PlatformXDirectoryCreationParams params) + : this.fromPlatform(PlatformXDirectory(params)); + + /// Constructs a [XDirectory] from a specific platform implementation. + const XDirectory.fromPlatform(PlatformXDirectory super.platform); + + /// Implementation of [PlatformXDirectory] for the current platform. + @override + PlatformXDirectory get platform => super.platform as PlatformXDirectory; + + /// Provides a nonnull platform class extension. + /// + /// Will throw an exception if the specified platform extension can not be + /// returned. + S getExtension() { + return platform.extension! as S; + } + + /// Attempt to provide the platform class extension. + /// + /// Returns null if the specified platform extension cannot be retrieved. + S? maybeGetExtension() { + return platform.extension is S ? platform.extension! as S : null; + } + + /// Lists the sub-directories and files of this directory. + Stream list() { + return platform.list(ListParams()).map(( + PlatformXFileEntity entity, + ) { + switch (entity) { + case PlatformXFile(): + if (entity case PlatformScopedStorageXFile()) { + return ScopedStorageXFile.fromPlatform(entity); + } + return XFile.fromPlatform(entity); + case PlatformXDirectory(): + if (entity case PlatformScopedStorageXDirectory()) { + return ScopedStorageXDirectory.fromPlatform(entity); + } + return XDirectory.fromPlatform(entity); + } + + return XFileEntity(entity); + }); + } +} diff --git a/packages/cross_file/cross_file/lib/src/cross_file.dart b/packages/cross_file/cross_file/lib/src/cross_file.dart new file mode 100644 index 000000000000..a4213fc405b1 --- /dev/null +++ b/packages/cross_file/cross_file/lib/src/cross_file.dart @@ -0,0 +1,132 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'cross_file_entity.dart'; + +/// A reference to a local data resource. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro cross_file.XFile.fromCreationParams} +/// +/// Below is an example of accessing the platform-specific extension for +/// the dart:io implementation of `cross_file`: +/// +/// ```dart +/// final XFile file = XFile.fromUri(Uri.file('/my/file.txt')); +/// +/// final IOXFileExtension? ioExtension = file.maybeGetExtension(); +/// if (ioExtension != null) { +/// print(ioExtension.file.path); +/// } +/// ``` +@immutable +class XFile extends XFileEntity { + /// Constructs a [XFile]. + /// + /// See [XFile.fromCreationParams] for setting parameters for a specific + /// platform. + XFile({required String uri}) + : this.fromCreationParams(PlatformXFileCreationParams(uri: uri)); + + /// Constructs a [XFile]. + /// + /// See [XFile.fromCreationParams] for setting parameters for a specific + /// platform. + XFile.fromUri(Uri uri) : this(uri: uri.toString()); + + /// Constructs a [XFile] from creation params for a specific platform. + /// + /// {@template cross_file.XFile.fromCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// the dart:io implementation of `cross_file`: + /// + /// ```dart + /// var params = const PlatformXFileCreationParams(uri: 'file:///my/file.txt'); + /// + /// if (CrossFilePlatform.instance is CrossFileIO) { + /// params = IOXFileCreationParams.fromCreationParams( + /// params, + /// ); + /// } + /// + /// final file = XFile.fromCreationParams(params); + /// ``` + /// {@endtemplate} + XFile.fromCreationParams(PlatformXFileCreationParams params) + : this.fromPlatform(PlatformXFile(params)); + + /// Constructs a [XFile] from a specific platform implementation. + const XFile.fromPlatform(PlatformXFile super.platform); + + /// Implementation of [XFile] for the current platform. + @override + PlatformXFile get platform => super.platform as PlatformXFile; + + /// Provides a nonnull platform class extension. + /// + /// Will throw an exception if the specified platform extension can not be + /// returned. + S getExtension() { + return platform.extension! as S; + } + + /// Attempt to provide the platform class extension. + /// + /// Returns null if the specified platform extension cannot be retrieved. + S? maybeGetExtension() { + return platform.extension is S ? platform.extension! as S : null; + } + + /// Date and time when the resource was last modified, if the information is + /// available. + Future lastModified() => platform.lastModified(); + + /// The length of the data represented by this uri, in bytes. + /// + /// Returns null if the information is not available. + Future length() => platform.length(); + + /// Whether the resource represented by this reference can be read. + Future canRead() => platform.canRead(); + + /// 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]) => + platform.openRead(start, 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() => platform.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}) => + platform.readAsString(encoding: encoding); + + /// The name of the resource represented by this object. + /// + /// The path is excluded from this value. + Future name() => platform.name(); +} diff --git a/packages/cross_file/cross_file/lib/src/cross_file_entity.dart b/packages/cross_file/cross_file/lib/src/cross_file_entity.dart new file mode 100644 index 000000000000..c6eb1152f8d5 --- /dev/null +++ b/packages/cross_file/cross_file/lib/src/cross_file_entity.dart @@ -0,0 +1,23 @@ +// 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/foundation.dart'; + +/// The common superclass for [XFile] and [XDirectory]. +@immutable +class XFileEntity { + /// Constructs a [XFileEntity]. + @protected + const XFileEntity(this.platform); + + /// Implementation of [XFileEntity] for the current platform. + final PlatformXFileEntity platform; + + /// A string used to reference the resource's location. + String get uri => platform.params.uri; + + /// Whether the resource represented by this reference exists. + Future exists() => platform.exists(); +} diff --git a/packages/cross_file/cross_file/lib/src/scoped_storage_cross_directory.dart b/packages/cross_file/cross_file/lib/src/scoped_storage_cross_directory.dart new file mode 100644 index 000000000000..5fb2c59e14bb --- /dev/null +++ b/packages/cross_file/cross_file/lib/src/scoped_storage_cross_directory.dart @@ -0,0 +1,75 @@ +// 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/foundation.dart' show immutable; + +import 'cross_directory.dart'; + +/// A reference to a directory (or folder) on the file system within a devices +/// scoped storage. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro cross_file.ScopedStorageXDirectory.fromCreationParams} +/// +/// Below is an example of accessing the platform-specific extension for +/// the Android implementation of `cross_file`: +/// +/// ```dart +/// final ScopedStorageXDirectory directory = ScopedStorageXDirectory(uri: 'content://my/dir'); +/// +/// final AndroidScopedStorageXDirectoryExtension? androidExtension = +/// file.maybeGetExtension(); +/// if (androidExtension != null) { +/// print(androidExtension.name()); +/// } +/// ``` +@immutable +class ScopedStorageXDirectory extends XDirectory { + /// Constructs a [ScopedStorageXDirectory]. + /// + /// See [ScopedStorageXDirectory.fromCreationParams] for setting parameters + /// for a specific platform. + ScopedStorageXDirectory({required String uri}) + : this.fromCreationParams( + PlatformScopedStorageXDirectoryCreationParams(uri: uri), + ); + + /// Constructs a [ScopedStorageXDirectory] from creation params for a specific + /// platform. + /// + /// {@template cross_file.ScopedStorageXDirectory.fromCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// the Android implementation of `cross_file`: + /// + /// ```dart + /// var params = const PlatformScopedStorageXDirectoryCreationParams(uri: 'content://my/docs'); + /// + /// if (CrossFilePlatform.instance is CrossFileAndroid) { + /// params = AndroidScopedStorageXDirectoryCreationParams.fromCreationParams( + /// params, + /// ); + /// } + /// + /// final file = ScopedStorageXDirectory.fromCreationParams(params); + /// ``` + /// {@endtemplate} + ScopedStorageXDirectory.fromCreationParams( + PlatformScopedStorageXDirectoryCreationParams params, + ) : this.fromPlatform(PlatformScopedStorageXDirectory(params)); + + /// Constructs a [ScopedStorageXDirectory] from a specific platform + /// implementation. + const ScopedStorageXDirectory.fromPlatform( + PlatformScopedStorageXDirectory super.platform, + ) : super.fromPlatform(); + + @override + PlatformScopedStorageXDirectory get platform => + super.platform as PlatformScopedStorageXDirectory; +} diff --git a/packages/cross_file/cross_file/lib/src/scoped_storage_cross_file.dart b/packages/cross_file/cross_file/lib/src/scoped_storage_cross_file.dart new file mode 100644 index 000000000000..f14a3bf3f95c --- /dev/null +++ b/packages/cross_file/cross_file/lib/src/scoped_storage_cross_file.dart @@ -0,0 +1,73 @@ +// 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/foundation.dart' show immutable; + +import 'cross_file.dart'; + +/// A reference to a local data resource within a devices scoped storage. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro cross_file.ScopedStorageXFile.fromCreationParams} +/// +/// Below is an example of accessing the platform-specific extension for +/// the Android implementation of `cross_file`: +/// +/// ```dart +/// final ScopedStorageXFile file = ScopedStorageXFile(uri: 'content://my/file.txt'); +/// +/// final AndroidScopedStorageXFileExtension? androidExtension = +/// file.maybeGetExtension(); +/// if (androidExtension != null) { +/// print(androidExtension.name()); +/// } +/// ``` +@immutable +class ScopedStorageXFile extends XFile { + /// Constructs a [ScopedStorageXFile]. + /// + /// See [ScopedStorageXFile.fromCreationParams] for setting parameters + /// for a specific platform. + ScopedStorageXFile({required String uri}) + : this.fromCreationParams( + PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + /// Constructs a [ScopedStorageXFile] from creation params for a specific + /// platform. + /// + /// {@template cross_file.ScopedStorageXFile.fromCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// the Android implementation of `cross_file`: + /// + /// ```dart + /// var params = const PlatformScopedStorageXFileCreationParams(uri: 'content://my/file.txt'); + /// + /// if (CrossFilePlatform.instance is CrossFileAndroid) { + /// params = AndroidScopedStorageXFileCreationParams.fromCreationParams( + /// params, + /// ); + /// } + /// + /// final file = ScopedStorageXFile.fromCreationParams(params); + /// ``` + /// {@endtemplate} + ScopedStorageXFile.fromCreationParams( + PlatformScopedStorageXFileCreationParams params, + ) : this.fromPlatform(PlatformScopedStorageXFile(params)); + + /// Constructs a [ScopedStorageXFile] from a specific platform implementation. + const ScopedStorageXFile.fromPlatform( + PlatformScopedStorageXFile super.platform, + ) : super.fromPlatform(); + + @override + PlatformScopedStorageXFile get platform => + super.platform as PlatformScopedStorageXFile; +} diff --git a/packages/cross_file/cross_file/pubspec.yaml b/packages/cross_file/cross_file/pubspec.yaml new file mode 100644 index 000000000000..8c6a64f1e136 --- /dev/null +++ b/packages/cross_file/cross_file/pubspec.yaml @@ -0,0 +1,62 @@ +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/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+1 + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +flutter: + plugin: + platforms: + android: + default_package: cross_file_android + ios: + default_package: cross_file_darwin + linux: + default_package: cross_file_io + macos: + default_package: cross_file_darwin + web: + default_package: cross_file_web + windows: + default_package: cross_file_io + +dependencies: + cross_file_android: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_android + cross_file_darwin: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_darwin + cross_file_io: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_io + cross_file_platform_interface: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_platform_interface + cross_file_web: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_web + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +topics: + - files + - cross-file diff --git a/packages/cross_file/cross_file/test/cross_directory_test.dart b/packages/cross_file/cross_file/test/cross_directory_test.dart new file mode 100644 index 000000000000..a368ce1cd288 --- /dev/null +++ b/packages/cross_file/cross_file/test/cross_directory_test.dart @@ -0,0 +1,53 @@ +// 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/cross_file.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_stubs.dart'; + +void main() { + group('XDirectory', () { + test('exists', () async { + const exists = true; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXDirectory: (PlatformXDirectoryCreationParams params) => + TestXDirectory(params, onExists: () async => exists), + ); + + final directory = XDirectory(uri: 'uri'); + + expect(await directory.exists(), exists); + }); + + test('list', () async { + final entities = [ + TestXFile(const PlatformXFileCreationParams(uri: 'uri1')), + TestXDirectory(const PlatformXDirectoryCreationParams(uri: 'uri2')), + TestXFileEntity(const PlatformXFileCreationParams(uri: 'uri3')), + ]; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXDirectory: (PlatformXDirectoryCreationParams params) => + TestXDirectory( + params, + onList: (ListParams params) => Stream.fromIterable(entities), + ), + ); + + final directory = XDirectory(uri: 'uri'); + + final List directoryEntities = await directory + .list() + .toList(); + expect(directoryEntities.length, entities.length); + expect(directoryEntities.first, isA()); + expect(directoryEntities.first.uri, entities.first.params.uri); + expect(directoryEntities[1], isA()); + expect(directoryEntities[1].uri, entities[1].params.uri); + expect(directoryEntities[2], isA()); + expect(directoryEntities[2].uri, entities[2].params.uri); + }); + }); +} diff --git a/packages/cross_file/cross_file/test/cross_file_test.dart b/packages/cross_file/cross_file/test/cross_file_test.dart new file mode 100644 index 000000000000..94aed4f5f023 --- /dev/null +++ b/packages/cross_file/cross_file/test/cross_file_test.dart @@ -0,0 +1,117 @@ +// 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:cross_file/cross_file.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_stubs.dart'; + +void main() { + group('XFile', () { + test('lastModified', () async { + final lastModified = DateTime.now(); + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onLastModified: () async => lastModified), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.lastModified(), lastModified); + }); + + test('length', () async { + const length = 42; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onLength: () async => length), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.length(), length); + }); + + test('openRead', () async { + final data = [ + Uint8List.fromList([5, 6]), + ]; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onOpenRead: () => Stream.fromIterable(data)), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.openRead().toList(), data); + }); + + test('readAsBytes', () async { + final bytes = Uint8List.fromList([1, 2, 3]); + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onReadAsBytes: () async => bytes), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.readAsBytes(), bytes); + }); + + test('readAsString', () async { + const message = 'Hello, World!'; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile( + params, + onReadAsString: ({required Encoding encoding}) async => message, + ), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.readAsString(), message); + }); + + test('canRead', () async { + const canRead = false; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onCanRead: () async => canRead), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.canRead(), canRead); + }); + + test('exists', () async { + const exists = true; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onExists: () async => exists), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.exists(), exists); + }); + + test('name', () async { + const name = 'name'; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformXFile: (PlatformXFileCreationParams params) => + TestXFile(params, onName: () async => name), + ); + + final file = XFile(uri: 'uri'); + + expect(await file.name(), name); + }); + }); +} diff --git a/packages/cross_file/cross_file/test/scoped_storage_cross_directory_test.dart b/packages/cross_file/cross_file/test/scoped_storage_cross_directory_test.dart new file mode 100644 index 000000000000..9d8b4ddc4f44 --- /dev/null +++ b/packages/cross_file/cross_file/test/scoped_storage_cross_directory_test.dart @@ -0,0 +1,62 @@ +// 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/cross_file.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_stubs.dart'; + +void main() { + group('XDirectory', () { + test('exists', () async { + const exists = true; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXDirectory: + (PlatformScopedStorageXDirectoryCreationParams params) => + TestScopedStorageXDirectory( + params, + onExists: () async => exists, + ), + ); + + final directory = ScopedStorageXDirectory(uri: 'uri'); + + expect(await directory.exists(), exists); + }); + + test('list', () async { + final entities = [ + TestScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: 'uri1'), + ), + TestScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams(uri: 'uri2'), + ), + TestXFileEntity(const PlatformXFileCreationParams(uri: 'uri3')), + ]; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXDirectory: + (PlatformScopedStorageXDirectoryCreationParams params) => + TestScopedStorageXDirectory( + params, + onList: (ListParams params) => Stream.fromIterable(entities), + ), + ); + + final directory = ScopedStorageXDirectory(uri: 'uri'); + + final List directoryEntities = await directory + .list() + .toList(); + expect(directoryEntities.length, entities.length); + expect(directoryEntities.first, isA()); + expect(directoryEntities.first.uri, entities.first.params.uri); + expect(directoryEntities[1], isA()); + expect(directoryEntities[1].uri, entities[1].params.uri); + expect(directoryEntities[2], isA()); + expect(directoryEntities[2].uri, entities[2].params.uri); + }); + }); +} diff --git a/packages/cross_file/cross_file/test/scoped_storage_cross_file_test.dart b/packages/cross_file/cross_file/test/scoped_storage_cross_file_test.dart new file mode 100644 index 000000000000..dbb729be6fc6 --- /dev/null +++ b/packages/cross_file/cross_file/test/scoped_storage_cross_file_test.dart @@ -0,0 +1,135 @@ +// 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:cross_file/cross_file.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_stubs.dart'; + +void main() { + group('XFile', () { + test('lastModified', () async { + final lastModified = DateTime.now(); + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile( + params, + onLastModified: () async => lastModified, + ), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.lastModified(), lastModified); + }); + + test('length', () async { + const length = 42; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile(params, onLength: () async => length), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.length(), length); + }); + + test('openRead', () async { + final data = [ + Uint8List.fromList([5, 6]), + ]; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile( + params, + onOpenRead: () => Stream.fromIterable(data), + ), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.openRead().toList(), data); + }); + + test('readAsBytes', () async { + final bytes = Uint8List.fromList([1, 2, 3]); + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile( + params, + onReadAsBytes: () async => bytes, + ), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.readAsBytes(), bytes); + }); + + test('readAsString', () async { + const message = 'Hello, World!'; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile( + params, + onReadAsString: ({required Encoding encoding}) async => + message, + ), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.readAsString(), message); + }); + + test('canRead', () async { + const canRead = false; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile(params, onCanRead: () async => canRead), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.canRead(), canRead); + }); + + test('exists', () async { + const exists = true; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile(params, onExists: () async => exists), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.exists(), exists); + }); + + test('name', () async { + const name = 'name'; + CrossFilePlatform.instance = TestCrossFilePlatform( + onCreatePlatformScopedStorageXFile: + (PlatformScopedStorageXFileCreationParams params) => + TestScopedStorageXFile(params, onName: () async => name), + ); + + final file = ScopedStorageXFile(uri: 'uri'); + + expect(await file.name(), name); + }); + }); +} diff --git a/packages/cross_file/cross_file/test/test_stubs.dart b/packages/cross_file/cross_file/test/test_stubs.dart new file mode 100644 index 000000000000..69337b87fb86 --- /dev/null +++ b/packages/cross_file/cross_file/test/test_stubs.dart @@ -0,0 +1,244 @@ +// 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:cross_file_platform_interface/cross_file_platform_interface.dart'; + +final class TestCrossFilePlatform extends CrossFilePlatform { + TestCrossFilePlatform({ + this.onCreatePlatformXFile, + this.onCreatePlatformXDirectory, + this.onCreatePlatformScopedStorageXFile, + this.onCreatePlatformScopedStorageXDirectory, + }); + + PlatformXFile Function(PlatformXFileCreationParams params)? + onCreatePlatformXFile; + + PlatformXDirectory Function(PlatformXDirectoryCreationParams params)? + onCreatePlatformXDirectory; + + PlatformScopedStorageXFile Function( + PlatformScopedStorageXFileCreationParams params, + )? + onCreatePlatformScopedStorageXFile; + + PlatformScopedStorageXDirectory Function( + PlatformScopedStorageXDirectoryCreationParams params, + )? + onCreatePlatformScopedStorageXDirectory; + + @override + PlatformXFile createPlatformXFile(PlatformXFileCreationParams params) { + return onCreatePlatformXFile?.call(params) ?? TestXFile(params); + } + + @override + PlatformXDirectory createPlatformXDirectory( + PlatformXDirectoryCreationParams params, + ) { + return onCreatePlatformXDirectory?.call(params) ?? TestXDirectory(params); + } + + @override + PlatformScopedStorageXFile createPlatformScopedStorageXFile( + PlatformScopedStorageXFileCreationParams params, + ) { + return onCreatePlatformScopedStorageXFile?.call(params) ?? + TestScopedStorageXFile(params); + } + + @override + PlatformScopedStorageXDirectory createPlatformScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams params, + ) { + return onCreatePlatformScopedStorageXDirectory?.call(params) ?? + TestScopedStorageXDirectory(params); + } +} + +final class TestXFile extends PlatformXFile { + TestXFile( + super.params, { + this.onCanRead, + this.onExists, + this.onLastModified, + this.onLength, + this.onName, + this.onOpenRead, + this.onReadAsBytes, + this.onReadAsString, + }) : super.implementation(); + + Future Function()? onCanRead; + Future Function()? onExists; + Future Function()? onLastModified; + Future Function()? onLength; + Future Function()? onName; + Stream Function()? onOpenRead; + Future Function()? onReadAsBytes; + Future Function({required Encoding encoding})? onReadAsString; + + @override + Future canRead() async { + return await onCanRead?.call() ?? false; + } + + @override + Future exists() async { + return await onExists?.call() ?? false; + } + + @override + Future lastModified() async { + return await onLastModified?.call(); + } + + @override + Future length() async { + return await onLength?.call(); + } + + @override + Future name() async { + return await onName?.call(); + } + + @override + Stream openRead([int? start, int? end]) async* { + if (onOpenRead != null) { + yield* onOpenRead!.call(); + } + } + + @override + Future readAsBytes() async { + return await onReadAsBytes?.call() ?? Uint8List(0); + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return await onReadAsString?.call(encoding: encoding) ?? ''; + } +} + +final class TestXDirectory extends PlatformXDirectory { + TestXDirectory(super.params, {this.onExists, this.onList}) + : super.implementation(); + + Future Function()? onExists; + Stream Function(ListParams params)? onList; + + @override + Future exists() async { + return await onExists?.call() ?? false; + } + + @override + Stream list(ListParams params) async* { + if (onList != null) { + yield* onList!.call(params); + } + } +} + +final class TestScopedStorageXFile extends PlatformScopedStorageXFile { + TestScopedStorageXFile( + super.params, { + this.onCanRead, + this.onExists, + this.onLastModified, + this.onLength, + this.onName, + this.onOpenRead, + this.onReadAsBytes, + this.onReadAsString, + }) : super.implementation(); + + Future Function()? onCanRead; + Future Function()? onExists; + Future Function()? onLastModified; + Future Function()? onLength; + Future Function()? onName; + Stream Function()? onOpenRead; + Future Function()? onReadAsBytes; + Future Function({required Encoding encoding})? onReadAsString; + + @override + Future canRead() async { + return await onCanRead?.call() ?? false; + } + + @override + Future exists() async { + return await onExists?.call() ?? false; + } + + @override + Future lastModified() async { + return await onLastModified?.call(); + } + + @override + Future length() async { + return await onLength?.call(); + } + + @override + Future name() async { + return await onName?.call(); + } + + @override + Stream openRead([int? start, int? end]) async* { + if (onOpenRead != null) { + yield* onOpenRead!.call(); + } + } + + @override + Future readAsBytes() async { + return await onReadAsBytes?.call() ?? Uint8List(0); + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return await onReadAsString?.call(encoding: encoding) ?? ''; + } +} + +final class TestScopedStorageXDirectory + extends PlatformScopedStorageXDirectory { + TestScopedStorageXDirectory(super.params, {this.onExists, this.onList}) + : super.implementation(); + + Future Function()? onExists; + Stream Function(ListParams params)? onList; + + @override + Future exists() async { + return await onExists?.call() ?? false; + } + + @override + Stream list(ListParams params) async* { + if (onList != null) { + yield* onList!.call(params); + } + } +} + +final class TestXFileEntity extends PlatformXFileEntity { + TestXFileEntity(super.params, {this.onExists}); + + Future Function()? onExists; + + @override + Future exists() async { + return await onExists?.call() ?? false; + } +} diff --git a/packages/cross_file/cross_file_android/CHANGELOG.md b/packages/cross_file/cross_file_android/CHANGELOG.md new file mode 100644 index 000000000000..d0bd041d0ff6 --- /dev/null +++ b/packages/cross_file/cross_file_android/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release. diff --git a/packages/cross_file/cross_file_android/LICENSE b/packages/cross_file/cross_file_android/LICENSE new file mode 100644 index 000000000000..29b709dac6c7 --- /dev/null +++ b/packages/cross_file/cross_file_android/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_android/README.md b/packages/cross_file/cross_file_android/README.md new file mode 100644 index 000000000000..68dd5ff78961 --- /dev/null +++ b/packages/cross_file/cross_file_android/README.md @@ -0,0 +1,15 @@ +# cross\_file\_android + +The Android implementation of [`cross_file`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `cross_file` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +[1]: https://pub.dev/packages/cross_file +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/cross_file/cross_file_android/android/build.gradle b/packages/cross_file/cross_file_android/android/build.gradle new file mode 100644 index 000000000000..2a720168e497 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/build.gradle @@ -0,0 +1,78 @@ +group = "dev.flutter.packages.cross_file_android" +version = "1.0" + +buildscript { + ext.kotlin_version = "2.2.21" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.13.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +android { + namespace = "dev.flutter.packages.cross_file_android" + compileSdk = flutter.compileSdkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + + defaultConfig { + minSdk = 24 + } + + dependencies { + implementation("androidx.documentfile:documentfile:1.1.0") + implementation("androidx.annotation:annotation:1.9.1") + implementation("androidx.core:core-ktx:1.13.0") + testImplementation("junit:junit:4.13.2") + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito.kotlin:mockito-kotlin:6.1.0") + testImplementation("org.mockito:mockito-inline:5.2.0") + testImplementation("androidx.test:core:1.7.0") + } + + lint { + checkAllWarnings = true + warningsAsErrors = true + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency', 'NewerVersionAvailable' + } + + testOptions { + unitTests.includeAndroidResources = true + unitTests.returnDefaultValues = true + unitTests.all { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/packages/cross_file/cross_file_android/android/settings.gradle b/packages/cross_file/cross_file_android/android/settings.gradle new file mode 100644 index 000000000000..3800a971764d --- /dev/null +++ b/packages/cross_file/cross_file_android/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cross_file_android' diff --git a/packages/cross_file/cross_file_android/android/src/main/AndroidManifest.xml b/packages/cross_file/cross_file_android/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..5ac3f7b5b2af --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/CrossFileAndroidPlugin.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/CrossFileAndroidPlugin.kt new file mode 100644 index 000000000000..2c2a7dcdb419 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/CrossFileAndroidPlugin.kt @@ -0,0 +1,25 @@ +// 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. + +package dev.flutter.packages.cross_file_android + +import io.flutter.embedding.engine.plugins.FlutterPlugin + +/** CrossFileAndroidPlugin */ +class CrossFileAndroidPlugin : FlutterPlugin { + + private lateinit var registrar: ProxyApiRegistrar + + override fun onAttachedToEngine(pluginBinding: FlutterPlugin.FlutterPluginBinding) { + registrar = + ProxyApiRegistrar(pluginBinding.binaryMessenger, context = pluginBinding.applicationContext) + registrar.setUp() + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + registrar.ignoreCallsToDart = true + registrar.tearDown() + registrar.instanceManager.clear() + } +} diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/ProxyApiRegistrar.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/ProxyApiRegistrar.kt new file mode 100644 index 000000000000..65b52b1eff15 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/ProxyApiRegistrar.kt @@ -0,0 +1,35 @@ +// 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. + +package dev.flutter.packages.cross_file_android + +import android.content.Context +import dev.flutter.packages.cross_file_android.proxies.AndroidLibraryPigeonProxyApiRegistrar +import dev.flutter.packages.cross_file_android.proxies.ContentResolverProxyApi +import dev.flutter.packages.cross_file_android.proxies.DocumentFileProxyApi +import dev.flutter.packages.cross_file_android.proxies.InputStreamProxyApi +import dev.flutter.packages.cross_file_android.proxies.PigeonApiContentResolver +import dev.flutter.packages.cross_file_android.proxies.PigeonApiDocumentFile +import dev.flutter.packages.cross_file_android.proxies.PigeonApiInputStream +import io.flutter.plugin.common.BinaryMessenger + +/** + * Implementation of [AndroidLibraryPigeonProxyApiRegistrar] that provides each ProxyApi + * implementation and any additional resources needed by an implementation. + */ +open class ProxyApiRegistrar(binaryMessenger: BinaryMessenger, var context: Context) : + AndroidLibraryPigeonProxyApiRegistrar(binaryMessenger) { + + override fun getPigeonApiDocumentFile(): PigeonApiDocumentFile { + return DocumentFileProxyApi(this) + } + + override fun getPigeonApiContentResolver(): PigeonApiContentResolver { + return ContentResolverProxyApi(this) + } + + override fun getPigeonApiInputStream(): PigeonApiInputStream { + return InputStreamProxyApi(this) + } +} diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/AndroidLibrary.g.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/AndroidLibrary.g.kt new file mode 100644 index 000000000000..10b5175cc75e --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/AndroidLibrary.g.kt @@ -0,0 +1,1108 @@ +// 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. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package dev.flutter.packages.cross_file_android.proxies + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private object AndroidLibraryPigeonUtils { + + fun createConnectionError(channelName: String): FlutterError { + return FlutterError( + "channel-error", "Unable to establish connection on channel: '$channelName'.", "") + } + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +/** + * Maintains instances used to communicate with the corresponding objects in Dart. + * + * Objects stored in this container are represented by an object in Dart that is also stored in an + * InstanceManager with the same identifier. + * + * When an instance is added with an identifier, either can be used to retrieve the other. + * + * Added instances are added as a weak reference and a strong reference. When the strong reference + * is removed with [remove] and the weak reference is deallocated, the + * `finalizationListener.onFinalize` is called with the instance's identifier. However, if the + * strong reference is removed and then the identifier is retrieved with the intention to pass the + * identifier to Dart (e.g. calling [getIdentifierForStrongReference]), the strong reference to the + * instance is recreated. The strong reference will then need to be removed manually again. + */ +@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate") +class AndroidLibraryPigeonInstanceManager( + private val finalizationListener: PigeonFinalizationListener +) { + /** Interface for listening when a weak reference of an instance is removed from the manager. */ + interface PigeonFinalizationListener { + fun onFinalize(identifier: Long) + } + + // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather + // than equality. + // + // Two `IdentityWeakReference`s are equal if either + // 1: `get()` returns the identical nonnull value for both references. + // 2: `get()` returns null for both references and the references are identical. + class IdentityWeakReference : java.lang.ref.WeakReference { + private val savedHashCode: Int + + constructor(instance: T) : this(instance, null) + + constructor(instance: T, queue: java.lang.ref.ReferenceQueue?) : super(instance, queue) { + savedHashCode = System.identityHashCode(instance) + } + + override fun equals(other: Any?): Boolean { + val instance = get() + if (instance != null) { + return other is IdentityWeakReference<*> && other.get() === instance + } + return other === this + } + + override fun hashCode(): Int { + return savedHashCode + } + } + + private val identifiers = java.util.WeakHashMap, Long>() + private val weakInstances = HashMap>() + private val strongInstances = HashMap() + private val referenceQueue = java.lang.ref.ReferenceQueue() + private val weakReferencesToIdentifiers = HashMap, Long>() + private val handler = android.os.Handler(android.os.Looper.getMainLooper()) + private val releaseAllFinalizedInstancesRunnable = Runnable { + this.releaseAllFinalizedInstances() + } + private var nextIdentifier: Long = minHostCreatedIdentifier + private var hasFinalizationListenerStopped = false + + /** + * Modifies the time interval used to define how often this instance removes garbage collected + * weak references to native Android objects that this instance was managing. + */ + var clearFinalizedWeakReferencesInterval: Long = 3000 + set(value) { + handler.removeCallbacks(releaseAllFinalizedInstancesRunnable) + field = value + releaseAllFinalizedInstances() + } + + init { + handler.postDelayed(releaseAllFinalizedInstancesRunnable, clearFinalizedWeakReferencesInterval) + } + + companion object { + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously from Dart. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + private const val minHostCreatedIdentifier: Long = 65536 + private const val tag = "PigeonInstanceManager" + + /** + * Instantiate a new manager with a listener for garbage collected weak references. + * + * When the manager is no longer needed, [stopFinalizationListener] must be called. + */ + fun create( + finalizationListener: PigeonFinalizationListener + ): AndroidLibraryPigeonInstanceManager { + return AndroidLibraryPigeonInstanceManager(finalizationListener) + } + } + + /** + * Removes `identifier` and return its associated strongly referenced instance, if present, from + * the manager. + */ + fun remove(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + return strongInstances.remove(identifier) as T? + } + + /** + * Retrieves the identifier paired with an instance, if present, otherwise `null`. + * + * If the manager contains a strong reference to `instance`, it will return the identifier + * associated with `instance`. If the manager contains only a weak reference to `instance`, a new + * strong reference to `instance` will be added and will need to be removed again with [remove]. + * + * If this method returns a nonnull identifier, this method also expects the Dart + * `AndroidLibraryPigeonInstanceManager` to have, or recreate, a weak reference to the Dart + * instance the identifier is associated with. + */ + fun getIdentifierForStrongReference(instance: Any?): Long? { + logWarningIfFinalizationListenerHasStopped() + if (instance == null) { + return null + } + val identifier = identifiers[IdentityWeakReference(instance)] + if (identifier != null) { + strongInstances[identifier] = instance + } + return identifier + } + + /** + * Adds a new instance that was instantiated from Dart. + * + * The same instance can be added multiple times, but each identifier must be unique. This allows + * two objects that are equivalent (e.g. the `equals` method returns true and their hashcodes are + * equal) to both be added. + * + * [identifier] must be >= 0 and unique. + */ + fun addDartCreatedInstance(instance: Any, identifier: Long) { + logWarningIfFinalizationListenerHasStopped() + addInstance(instance, identifier) + } + + /** + * Adds a new unique instance that was instantiated from the host platform. + * + * If the manager contains [instance], this returns the corresponding identifier. If the manager + * does not contain [instance], this adds the instance and returns a unique identifier for that + * [instance]. + */ + fun addHostCreatedInstance(instance: Any): Long { + logWarningIfFinalizationListenerHasStopped() + require(!containsInstance(instance)) { + "Instance of ${instance.javaClass} has already been added." + } + val identifier = nextIdentifier++ + addInstance(instance, identifier) + return identifier + } + + /** Retrieves the instance associated with identifier, if present, otherwise `null`. */ + fun getInstance(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + val instance = weakInstances[identifier] as IdentityWeakReference? + return instance?.get() + } + + /** Returns whether this manager contains the given `instance`. */ + fun containsInstance(instance: Any?): Boolean { + logWarningIfFinalizationListenerHasStopped() + return instance != null && identifiers.containsKey(IdentityWeakReference(instance)) + } + + /** + * Stops the periodic run of the [PigeonFinalizationListener] for instances that have been garbage + * collected. + * + * The InstanceManager can continue to be used, but the [PigeonFinalizationListener] will no + * longer be called and methods will log a warning. + */ + fun stopFinalizationListener() { + handler.removeCallbacks(releaseAllFinalizedInstancesRunnable) + hasFinalizationListenerStopped = true + } + + /** + * Removes all of the instances from this manager. + * + * The manager will be empty after this call returns. + */ + fun clear() { + identifiers.clear() + weakInstances.clear() + strongInstances.clear() + weakReferencesToIdentifiers.clear() + } + + /** + * Whether the [PigeonFinalizationListener] is still being called for instances that are garbage + * collected. + * + * See [stopFinalizationListener]. + */ + fun hasFinalizationListenerStopped(): Boolean { + return hasFinalizationListenerStopped + } + + private fun releaseAllFinalizedInstances() { + if (hasFinalizationListenerStopped()) { + return + } + var reference: IdentityWeakReference? + while ((referenceQueue.poll() as IdentityWeakReference?).also { reference = it } != null) { + val identifier = weakReferencesToIdentifiers.remove(reference) + if (identifier != null) { + weakInstances.remove(identifier) + strongInstances.remove(identifier) + finalizationListener.onFinalize(identifier) + } + } + handler.postDelayed(releaseAllFinalizedInstancesRunnable, clearFinalizedWeakReferencesInterval) + } + + private fun addInstance(instance: Any, identifier: Long) { + require(identifier >= 0) { "Identifier must be >= 0: $identifier" } + require(!weakInstances.containsKey(identifier)) { + "Identifier has already been added: $identifier" + } + val weakReference = IdentityWeakReference(instance, referenceQueue) + identifiers[weakReference] = identifier + weakInstances[identifier] = weakReference + weakReferencesToIdentifiers[weakReference] = identifier + strongInstances[identifier] = instance + } + + private fun logWarningIfFinalizationListenerHasStopped() { + if (hasFinalizationListenerStopped()) { + Log.w( + tag, + "The manager was used after calls to the PigeonFinalizationListener has been stopped.") + } + } +} + +/** Generated API for managing the Dart and native `InstanceManager`s. */ +private class AndroidLibraryPigeonInstanceManagerApi(val binaryMessenger: BinaryMessenger) { + companion object { + /** The codec used by AndroidLibraryPigeonInstanceManagerApi. */ + val codec: MessageCodec by lazy { AndroidLibraryPigeonCodec() } + + /** + * Sets up an instance of `AndroidLibraryPigeonInstanceManagerApi` to handle messages from the + * `binaryMessenger`. + */ + fun setUpMessageHandlers( + binaryMessenger: BinaryMessenger, + instanceManager: AndroidLibraryPigeonInstanceManager? + ) { + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference", + codec) + if (instanceManager != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val identifierArg = args[0] as Long + val wrapped: List = + try { + instanceManager.remove(identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.clear", + codec) + if (instanceManager != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + instanceManager.clear() + listOf(null) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + fun removeStrongReference(identifierArg: Long, callback: (Result) -> Unit) { + val channelName = + "dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Provides implementations for each ProxyApi implementation and provides access to resources needed + * by any implementation. + */ +abstract class AndroidLibraryPigeonProxyApiRegistrar(val binaryMessenger: BinaryMessenger) { + /** Whether APIs should ignore calling to Dart. */ + public var ignoreCallsToDart = false + val instanceManager: AndroidLibraryPigeonInstanceManager + private var _codec: MessageCodec? = null + val codec: MessageCodec + get() { + if (_codec == null) { + _codec = AndroidLibraryPigeonProxyApiBaseCodec(this) + } + return _codec!! + } + + init { + val api = AndroidLibraryPigeonInstanceManagerApi(binaryMessenger) + instanceManager = + AndroidLibraryPigeonInstanceManager.create( + object : AndroidLibraryPigeonInstanceManager.PigeonFinalizationListener { + override fun onFinalize(identifier: Long) { + api.removeStrongReference(identifier) { + if (it.isFailure) { + Log.e( + "PigeonProxyApiRegistrar", + "Failed to remove Dart strong reference with identifier: $identifier") + } + } + } + }) + } + /** + * An implementation of [PigeonApiDocumentFile] used to add a new Dart instance of `DocumentFile` + * to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiDocumentFile(): PigeonApiDocumentFile + + /** + * An implementation of [PigeonApiContentResolver] used to add a new Dart instance of + * `ContentResolver` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiContentResolver(): PigeonApiContentResolver + + /** + * An implementation of [PigeonApiInputStream] used to add a new Dart instance of `InputStream` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiInputStream(): PigeonApiInputStream + + fun setUp() { + AndroidLibraryPigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, instanceManager) + PigeonApiDocumentFile.setUpMessageHandlers(binaryMessenger, getPigeonApiDocumentFile()) + PigeonApiContentResolver.setUpMessageHandlers(binaryMessenger, getPigeonApiContentResolver()) + PigeonApiInputStream.setUpMessageHandlers(binaryMessenger, getPigeonApiInputStream()) + } + + fun tearDown() { + AndroidLibraryPigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, null) + PigeonApiDocumentFile.setUpMessageHandlers(binaryMessenger, null) + PigeonApiContentResolver.setUpMessageHandlers(binaryMessenger, null) + PigeonApiInputStream.setUpMessageHandlers(binaryMessenger, null) + } +} + +private class AndroidLibraryPigeonProxyApiBaseCodec( + val registrar: AndroidLibraryPigeonProxyApiRegistrar +) : AndroidLibraryPigeonCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + val identifier: Long = readValue(buffer) as Long + val instance: Any? = registrar.instanceManager.getInstance(identifier) + if (instance == null) { + Log.e("PigeonProxyApiBaseCodec", "Failed to find instance with identifier: $identifier") + } + return instance + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + if (value is Boolean || + value is ByteArray || + value is Double || + value is DoubleArray || + value is FloatArray || + value is Int || + value is IntArray || + value is List<*> || + value is Long || + value is LongArray || + value is Map<*, *> || + value is String || + value == null) { + super.writeValue(stream, value) + return + } + + fun logNewInstanceFailure(apiName: String, value: Any, exception: Throwable?) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of $apiName: $value. $exception") + } + + if (value is androidx.documentfile.provider.DocumentFile) { + registrar.getPigeonApiDocumentFile().pigeon_newInstance(value) { + if (it.isFailure) { + logNewInstanceFailure("DocumentFile", value, it.exceptionOrNull()) + } + } + } else if (value is android.content.ContentResolver) { + registrar.getPigeonApiContentResolver().pigeon_newInstance(value) { + if (it.isFailure) { + logNewInstanceFailure("ContentResolver", value, it.exceptionOrNull()) + } + } + } else if (value is java.io.InputStream) { + registrar.getPigeonApiInputStream().pigeon_newInstance(value) { + if (it.isFailure) { + logNewInstanceFailure("InputStream", value, it.exceptionOrNull()) + } + } + } + + when { + registrar.instanceManager.containsInstance(value) -> { + stream.write(128) + writeValue(stream, registrar.instanceManager.getIdentifierForStrongReference(value)) + } + else -> + throw IllegalArgumentException( + "Unsupported value: '$value' of type '${value.javaClass.name}'") + } + } +} + +private open class AndroidLibraryPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + +/** + * Representation of a document backed by either a android.provider.DocumentsProvider or a raw file + * on disk. + * + * See https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiDocumentFile( + open val pigeonRegistrar: AndroidLibraryPigeonProxyApiRegistrar +) { + /** Create a DocumentFile representing the single document at the given Uri. */ + abstract fun fromSingleUri(singleUri: String): androidx.documentfile.provider.DocumentFile + + /** Create a DocumentFile representing the document tree rooted at the given Uri. */ + abstract fun fromTreeUri(treeUri: String): androidx.documentfile.provider.DocumentFile + + /** Indicates whether the current context is allowed to read from this file. */ + abstract fun canRead(pigeon_instance: androidx.documentfile.provider.DocumentFile): Boolean + + /** Deletes this file. */ + abstract fun delete(pigeon_instance: androidx.documentfile.provider.DocumentFile): Boolean + + /** Returns a boolean indicating whether this file can be found. */ + abstract fun exists(pigeon_instance: androidx.documentfile.provider.DocumentFile): Boolean + + /** + * Returns the time when this file was last modified, measured in milliseconds since January 1st, + * 1970, midnight. + */ + abstract fun lastModified(pigeon_instance: androidx.documentfile.provider.DocumentFile): Long + + /** Returns the length of this file in bytes. */ + abstract fun length(pigeon_instance: androidx.documentfile.provider.DocumentFile): Long + + /** Indicates if this file represents a *file*. */ + abstract fun isFile(pigeon_instance: androidx.documentfile.provider.DocumentFile): Boolean + + /** Indicates if this file represents a *directory*. */ + abstract fun isDirectory(pigeon_instance: androidx.documentfile.provider.DocumentFile): Boolean + + /** Returns an list of files contained in the directory represented by this file. */ + abstract fun listFiles( + pigeon_instance: androidx.documentfile.provider.DocumentFile + ): List + + /** A Uri for the underlying document represented by this file. */ + abstract fun getUri(pigeon_instance: androidx.documentfile.provider.DocumentFile): String + + /** Returns the display name of this document. */ + abstract fun getName(pigeon_instance: androidx.documentfile.provider.DocumentFile): String? + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiDocumentFile?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.fromSingleUri", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val singleUriArg = args[1] as String + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.fromSingleUri(singleUriArg), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.fromTreeUri", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val treeUriArg = args[1] as String + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.fromTreeUri(treeUriArg), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.canRead", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.canRead(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.DocumentFile.delete", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.delete(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.DocumentFile.exists", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.exists(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.lastModified", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.lastModified(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.DocumentFile.length", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.length(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.DocumentFile.isFile", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.isFile(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.isDirectory", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.isDirectory(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.listFiles", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.listFiles(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.DocumentFile.getUri", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.getUri(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.DocumentFile.getName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as androidx.documentfile.provider.DocumentFile + val wrapped: List = + try { + listOf(api.getName(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of DocumentFile and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.documentfile.provider.DocumentFile, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + FlutterError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.cross_file_android.DocumentFile.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This class provides applications access to the content model. + * + * See https://developer.android.com/reference/kotlin/android/content/ContentResolver + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiContentResolver( + open val pigeonRegistrar: AndroidLibraryPigeonProxyApiRegistrar +) { + /** Helper field for accessing the `ContentResolver` from the current Android `Context`. */ + abstract fun instance(): android.content.ContentResolver + + /** Open a stream on to the content associated with a content URI. */ + abstract fun openInputStream( + pigeon_instance: android.content.ContentResolver, + uri: String + ): java.io.InputStream? + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiContentResolver?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.ContentResolver.instance", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.instance(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.ContentResolver.openInputStream", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.content.ContentResolver + val uriArg = args[1] as String + val wrapped: List = + try { + listOf(api.openInputStream(pigeon_instanceArg, uriArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ContentResolver and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.content.ContentResolver, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + FlutterError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.cross_file_android.ContentResolver.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This abstract class is the superclass of all classes representing an input stream of bytes. + * + * See https://developer.android.com/reference/java/io/InputStream. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiInputStream( + open val pigeonRegistrar: AndroidLibraryPigeonProxyApiRegistrar +) { + /** Reads some number of bytes from the input stream and stores them into the returns them. */ + abstract fun readBytes(pigeon_instance: java.io.InputStream, len: Long): ByteArray + + /** Reads all remaining bytes from the input stream. */ + abstract fun readAllBytes(pigeon_instance: java.io.InputStream): ByteArray + + /** Skips over and discards n bytes of data from this input stream. */ + abstract fun skip(pigeon_instance: java.io.InputStream, n: Long): Long + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiInputStream?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.InputStream.readBytes", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as java.io.InputStream + val lenArg = args[1] as Long + val wrapped: List = + try { + listOf(api.readBytes(pigeon_instanceArg, lenArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.cross_file_android.InputStream.readAllBytes", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as java.io.InputStream + val wrapped: List = + try { + listOf(api.readAllBytes(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.cross_file_android.InputStream.skip", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as java.io.InputStream + val nArg = args[1] as Long + val wrapped: List = + try { + listOf(api.skip(pigeon_instanceArg, nArg)) + } catch (exception: Throwable) { + AndroidLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of InputStream and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: java.io.InputStream, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + FlutterError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.cross_file_android.InputStream.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverProxyApi.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverProxyApi.kt new file mode 100644 index 000000000000..bd71a53188f2 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverProxyApi.kt @@ -0,0 +1,27 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import android.content.ContentResolver +import androidx.core.net.toUri +import dev.flutter.packages.cross_file_android.ProxyApiRegistrar +import java.io.InputStream + +/** + * ProxyApi implementation for [ContentResolver]. + * + * This class may handle instantiating native object instances that are attached to a Dart instance + * or handle method calls on the associated native class or an instance of that class. + */ +class ContentResolverProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiContentResolver(pigeonRegistrar) { + override fun instance(): ContentResolver { + return pigeonRegistrar.context.contentResolver + } + + override fun openInputStream(pigeon_instance: ContentResolver, uri: String): InputStream? { + return pigeon_instance.openInputStream(uri.toUri()) + } +} diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileProxyApi.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileProxyApi.kt new file mode 100644 index 000000000000..1f3ebd2bb58c --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileProxyApi.kt @@ -0,0 +1,67 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import androidx.core.net.toUri +import androidx.documentfile.provider.DocumentFile +import dev.flutter.packages.cross_file_android.ProxyApiRegistrar + +/** + * ProxyApi implementation for [DocumentFile]. + * + * This class may handle instantiating native object instances that are attached to a Dart instance + * or handle method calls on the associated native class or an instance of that class. + */ +class DocumentFileProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiDocumentFile(pigeonRegistrar) { + override fun fromSingleUri(singleUri: String): DocumentFile { + // Only returns null on platforms below Android 19. + return DocumentFile.fromSingleUri(pigeonRegistrar.context, singleUri.toUri())!! + } + + override fun fromTreeUri(treeUri: String): DocumentFile { + return DocumentFile.fromTreeUri(pigeonRegistrar.context, treeUri.toUri())!! + } + + override fun canRead(pigeon_instance: DocumentFile): Boolean { + return pigeon_instance.canRead() + } + + override fun delete(pigeon_instance: DocumentFile): Boolean { + return pigeon_instance.delete() + } + + override fun exists(pigeon_instance: DocumentFile): Boolean { + return pigeon_instance.exists() + } + + override fun lastModified(pigeon_instance: DocumentFile): Long { + return pigeon_instance.lastModified() + } + + override fun length(pigeon_instance: DocumentFile): Long { + return pigeon_instance.length() + } + + override fun isFile(pigeon_instance: DocumentFile): Boolean { + return pigeon_instance.isFile + } + + override fun isDirectory(pigeon_instance: DocumentFile): Boolean { + return pigeon_instance.isDirectory + } + + override fun listFiles(pigeon_instance: DocumentFile): List { + return pigeon_instance.listFiles().toList() + } + + override fun getUri(pigeon_instance: DocumentFile): String { + return pigeon_instance.uri.toString() + } + + override fun getName(pigeon_instance: DocumentFile): String? { + return pigeon_instance.name + } +} diff --git a/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamProxyApi.kt b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamProxyApi.kt new file mode 100644 index 000000000000..65cbe727fa28 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamProxyApi.kt @@ -0,0 +1,38 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import dev.flutter.packages.cross_file_android.ProxyApiRegistrar +import java.io.InputStream + +/** + * ProxyApi implementation for [InputStream]. + * + * This class may handle instantiating native object instances that are attached to a Dart instance + * or handle method calls on the associated native class or an instance of that class. + */ +class InputStreamProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiInputStream(pigeonRegistrar) { + override fun readBytes( + pigeon_instance: InputStream, + len: Long, + ): ByteArray { + val bytes = ByteArray(len.toInt()) + val bytesRead = pigeon_instance.read(bytes) + if (bytesRead == -1) { + return byteArrayOf() + } else { + return bytes.copyOfRange(0, bytesRead) + } + } + + override fun readAllBytes(pigeon_instance: InputStream): ByteArray { + return pigeon_instance.readBytes() + } + + override fun skip(pigeon_instance: InputStream, n: Long): Long { + return pigeon_instance.skip(n) + } +} diff --git a/packages/cross_file/cross_file_android/android/src/test/kotlin/android/net/Uri.kt b/packages/cross_file/cross_file_android/android/src/test/kotlin/android/net/Uri.kt new file mode 100644 index 000000000000..53b3b85d7811 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/test/kotlin/android/net/Uri.kt @@ -0,0 +1,23 @@ +// 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. + +package android.net + +/** + * Redeclaration of Uri that works for tests. + * + * Without this redeclaration, `Uri.parse` always returns null. + */ +data class Uri(val uri: String) { + companion object { + @JvmStatic + fun parse(value: String): Uri { + return Uri(value) + } + } + + override fun toString(): String { + return this.uri + } +} diff --git a/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/TestProxyApiRegistrar.kt b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/TestProxyApiRegistrar.kt new file mode 100644 index 000000000000..eea7e75059b1 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/TestProxyApiRegistrar.kt @@ -0,0 +1,9 @@ +// 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. + +package dev.flutter.packages.cross_file_android + +import org.mockito.kotlin.mock + +class TestProxyApiRegistrar : ProxyApiRegistrar(mock(), mock()) diff --git a/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverTest.kt b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverTest.kt new file mode 100644 index 000000000000..fe48009226ce --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/ContentResolverTest.kt @@ -0,0 +1,39 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import android.content.ContentResolver +import android.net.Uri +import dev.flutter.packages.cross_file_android.TestProxyApiRegistrar +import java.io.InputStream +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class ContentResolverTest { + @Test + fun instance() { + val registrar = TestProxyApiRegistrar() + val api = registrar.getPigeonApiContentResolver() + + val instance = mock() + whenever(registrar.context.contentResolver).thenReturn(instance) + + assertEquals(instance, api.instance()) + } + + @Test + fun openInputStream() { + val api = TestProxyApiRegistrar().getPigeonApiContentResolver() + + val instance = mock() + val uri = Uri("myString") + val value = mock() + whenever(instance.openInputStream(uri)).thenReturn(value) + + assertEquals(value, api.openInputStream(instance, uri.toString())) + } +} diff --git a/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileTest.kt b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileTest.kt new file mode 100644 index 000000000000..fa6d193488d3 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/DocumentFileTest.kt @@ -0,0 +1,158 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import dev.flutter.packages.cross_file_android.TestProxyApiRegistrar +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito.mockStatic +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class DocumentFileTest { + @Test + fun fromSingleUri() { + val registrar = TestProxyApiRegistrar() + val api = registrar.getPigeonApiDocumentFile() + + mockStatic(DocumentFile::class.java).use { mockedStatic -> + val instance = mock() + val singleUri = Uri("myString") + mockedStatic + .`when` { DocumentFile.fromSingleUri(registrar.context, singleUri) } + .thenReturn(instance) + + assertEquals(instance, api.fromSingleUri(singleUri.toString())) + } + } + + @Test + fun fromTreeUri() { + val registrar = TestProxyApiRegistrar() + val api = registrar.getPigeonApiDocumentFile() + + mockStatic(DocumentFile::class.java).use { mockedStatic -> + val instance = mock() + val treeUri = Uri("myString") + mockedStatic + .`when` { DocumentFile.fromTreeUri(registrar.context, treeUri) } + .thenReturn(instance) + + assertEquals(instance, api.fromTreeUri(treeUri.toString())) + } + } + + @Test + fun canRead() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = true + whenever(instance.canRead()).thenReturn(value) + + assertEquals(value, api.canRead(instance)) + } + + @Test + fun delete() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = true + whenever(instance.delete()).thenReturn(value) + + assertEquals(value, api.delete(instance)) + } + + @Test + fun exists() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = true + whenever(instance.exists()).thenReturn(value) + + assertEquals(value, api.exists(instance)) + } + + @Test + fun lastModified() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = 0L + whenever(instance.lastModified()).thenReturn(value) + + assertEquals(value, api.lastModified(instance)) + } + + @Test + fun length() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = 0L + whenever(instance.length()).thenReturn(value) + + assertEquals(value, api.length(instance)) + } + + @Test + fun isFile() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = true + whenever(instance.isFile).thenReturn(value) + + assertEquals(value, api.isFile(instance)) + } + + @Test + fun isDirectory() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = true + whenever(instance.isDirectory).thenReturn(value) + + assertEquals(value, api.isDirectory(instance)) + } + + @Test + fun listFiles() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = listOf(mock()) + whenever(instance.listFiles()).thenReturn(value.toTypedArray()) + + assertEquals(value, api.listFiles(instance)) + } + + @Test + fun getUri() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = Uri("myString") + whenever(instance.uri).thenReturn(value) + + assertEquals(value.toString(), api.getUri(instance)) + } + + @Test + fun getName() { + val api = TestProxyApiRegistrar().getPigeonApiDocumentFile() + + val instance = mock() + val value = "name" + whenever(instance.name).thenReturn(value) + + assertEquals(value, api.getName(instance)) + } +} diff --git a/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamTest.kt b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamTest.kt new file mode 100644 index 000000000000..9463f839cff2 --- /dev/null +++ b/packages/cross_file/cross_file_android/android/src/test/kotlin/dev/flutter/packages/cross_file_android/proxies/InputStreamTest.kt @@ -0,0 +1,55 @@ +// 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. + +package dev.flutter.packages.cross_file_android.proxies + +import dev.flutter.packages.cross_file_android.TestProxyApiRegistrar +import java.io.ByteArrayInputStream +import java.io.InputStream +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.ArgumentCaptor +import org.mockito.kotlin.capture +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class InputStreamTest { + @Test + fun readBytes() { + val api = TestProxyApiRegistrar().getPigeonApiInputStream() + + val instance = mock() + val bytesCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + val len = 3 + val value = 2 + whenever(instance.read(bytesCaptor.capture())).thenReturn(value) + + assertEquals(api.readBytes(instance, len.toLong()).size, value) + assertEquals(bytesCaptor.value.size, len) + } + + @Test + fun readAllBytes() { + val api = TestProxyApiRegistrar().getPigeonApiInputStream() + + val value = byteArrayOf(0xA1.toByte()) + val instance = ByteArrayInputStream(value) + + val result = api.readAllBytes(instance) + assertEquals(value.size, result.size) + assertEquals(value.first(), result.first()) + } + + @Test + fun skip() { + val api = TestProxyApiRegistrar().getPigeonApiInputStream() + + val instance = mock() + val n = 5L + val value = 6L + whenever(instance.skip(n)).thenReturn(value) + + assertEquals(value, api.skip(instance, n)) + } +} diff --git a/packages/cross_file/cross_file_android/example/README.md b/packages/cross_file/cross_file_android/example/README.md new file mode 100644 index 000000000000..96b8bb17dbff --- /dev/null +++ b/packages/cross_file/cross_file_android/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/packages/cross_file/cross_file_android/example/android/app/build.gradle.kts b/packages/cross_file/cross_file_android/example/android/app/build.gradle.kts new file mode 100644 index 000000000000..e019b79077ea --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/build.gradle.kts @@ -0,0 +1,66 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +val localProperties = Properties() +val localPropertiesFile = rootProject.file("local.properties") + +if (localPropertiesFile.exists()) { + localPropertiesFile.reader(Charsets.UTF_8).use { reader -> + localProperties.load(reader) + } +} + +val flutterVersionCode: String = localProperties.getProperty("flutter.versionCode") ?: "1" +val flutterVersionName: String = localProperties.getProperty("flutter.versionName") ?: "1.0" + +android { + namespace = "dev.flutter.packages.cross_file_android_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + } + } + + defaultConfig { + applicationId = "dev.flutter.packages.cross_file_android_example" + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + signingConfig = signingConfigs.getByName("debug") + } + } +} + +dependencies { + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test:runner:1.2.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0") + api("androidx.test:core:1.4.0") +} + +flutter { + source = "../.." +} diff --git a/packages/cross_file/cross_file_android/example/android/app/src/androidTest/kotlin/dev/flutter/packages/cross_file_android_example/MainActivityTest.kt b/packages/cross_file/cross_file_android/example/android/app/src/androidTest/kotlin/dev/flutter/packages/cross_file_android_example/MainActivityTest.kt new file mode 100644 index 000000000000..a2e2b13340c6 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/androidTest/kotlin/dev/flutter/packages/cross_file_android_example/MainActivityTest.kt @@ -0,0 +1,17 @@ +// 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. + +package dev.flutter.packages.cross_file_android_example + +import androidx.test.rule.ActivityTestRule +import dev.flutter.plugins.integration_test.FlutterTestRunner +import io.flutter.plugins.DartIntegrationTest +import org.junit.Rule +import org.junit.runner.RunWith + +@DartIntegrationTest +@RunWith(FlutterTestRunner::class) +class MainActivityTest { + @JvmField @Rule var rule = ActivityTestRule(MainActivity::class.java) +} diff --git a/packages/cross_file/cross_file_android/example/android/app/src/debug/AndroidManifest.xml b/packages/cross_file/cross_file_android/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000000..6601c9953612 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/AndroidManifest.xml b/packages/cross_file/cross_file_android/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..3d3a2a209ab2 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/DriverExtensionActivity.kt b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/DriverExtensionActivity.kt new file mode 100644 index 000000000000..02f48723a431 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/DriverExtensionActivity.kt @@ -0,0 +1,10 @@ +// 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. + +package dev.flutter.packages.cross_file_android_example + +import io.flutter.embedding.android.FlutterActivity + +/** Test Activity that sets the name of the Dart method entrypoint in the manifest. */ +class DriverExtensionActivity : FlutterActivity() diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/MainActivity.kt b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/MainActivity.kt new file mode 100644 index 000000000000..fac8cbd08c60 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/dev/flutter/packages/cross_file_android_example/MainActivity.kt @@ -0,0 +1,9 @@ +// 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. + +package dev.flutter.packages.cross_file_android_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt new file mode 100644 index 000000000000..5d0a4557390d --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt @@ -0,0 +1,16 @@ +// 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. + +package io.flutter.plugins + +/* + * Annotation to aid repository tooling in determining if a test is + * a native java unit test or a java class with a dart integration. + * + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests + * for more information. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +annotation class DartIntegrationTest diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000000..f74085f3f6a2 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable/launch_background.xml b/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..09d4391482be Binary files /dev/null and b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/cross_file/cross_file_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/values-night/styles.xml b/packages/cross_file/cross_file_android/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000000..06952be745f9 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/main/res/values/styles.xml b/packages/cross_file/cross_file_android/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..cb1ef88056ed --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/cross_file/cross_file_android/example/android/app/src/profile/AndroidManifest.xml b/packages/cross_file/cross_file_android/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000000..399f6981d5d3 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/cross_file/cross_file_android/example/android/build.gradle b/packages/cross_file/cross_file_android/example/android/build.gradle new file mode 100644 index 000000000000..988fd1c987e9 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/build.gradle @@ -0,0 +1,39 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} + +allprojects { + repositories { + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. + def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' + if (System.getenv().containsKey(artifactRepoKey)) { + println "Using artifact hub" + maven { url System.getenv(artifactRepoKey) } + } + google() + mavenCentral() + } +} + +gradle.projectsEvaluated { + project(":cross_file_android") { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "-Werror" + } + } +} diff --git a/packages/cross_file/cross_file_android/example/android/gradle.properties b/packages/cross_file/cross_file_android/example/android/gradle.properties new file mode 100644 index 000000000000..fbee1d8cdafc --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/packages/cross_file/cross_file_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/cross_file/cross_file_android/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..02767eb1ca47 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/packages/cross_file/cross_file_android/example/android/settings.gradle.kts b/packages/cross_file/cross_file_android/example/android/settings.gradle.kts new file mode 100644 index 000000000000..fb605bc840f7 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.9.1" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/packages/cross_file/cross_file_android/example/integration_test/plugin_integration_test.dart b/packages/cross_file/cross_file_android/example/integration_test/plugin_integration_test.dart new file mode 100644 index 000000000000..7a43ba37cc41 --- /dev/null +++ b/packages/cross_file/cross_file_android/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,23 @@ +// 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_android_example/main.dart' as app; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +/// Entry point for integration tests that require espresso. +@pragma('vm:entry-point') +void integrationTestMain() { + enableFlutterDriverExtension(); + app.main(); +} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // Since this test is lacking integration tests, this test ensures the example + // app can be launched on an emulator/device. + testWidgets('Launch Test', (WidgetTester tester) async {}); +} diff --git a/packages/cross_file/cross_file_android/example/lib/main.dart b/packages/cross_file/cross_file_android/example/lib/main.dart new file mode 100644 index 000000000000..d4afa2abd81b --- /dev/null +++ b/packages/cross_file/cross_file_android/example/lib/main.dart @@ -0,0 +1,141 @@ +// 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:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:mime/mime.dart' as mime; + +/// Entry point for integration tests that require espresso. +@pragma('vm:entry-point') +void integrationTestMain() { + enableFlutterDriverExtension(); + main(); +} + +void main() { + runApp(const MaterialApp(home: FileOpenScreen())); +} + +/// Example screen to open a file selector and display it. +class FileOpenScreen extends StatelessWidget { + /// Constructs a [FileOpenScreen]. + const FileOpenScreen({super.key}); + + Future _openFile(BuildContext context) async { + final XFile? file = await openFile(); + + if (file != null) { + final String filename = await file.name() ?? file.uri; + + switch (mime.lookupMimeType(filename)) { + case final String mimeType when mimeType.startsWith('text'): + final String fileContents = await file.readAsString(); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(filename: filename, fileContents: fileContents), + ); + } + case _: + debugPrint('File Uri: ${file.uri}'); + debugPrint('Filename: $filename'); + debugPrint('Can Read File: ${await file.canRead()}'); + debugPrint('File Length: ${await file.length()}'); + debugPrint('File Last Modified: ${await file.lastModified()}'); + return; + } + } else { + debugPrint('No file selected.'); + } + } + + Future _openDirectory() async { + final XDirectory? directory = await getDirectoryPath(); + + if (directory != null) { + debugPrint('Directory Uri: ${directory.uri}'); + debugPrint('Directory exists: ${await directory.exists()}'); + + debugPrint('List of Entities:'); + await for (final XFileEntity entity in directory.list()) { + switch (entity) { + case final XFile file: + final String filename = await file.name() ?? file.uri; + debugPrint('\tFile: $filename'); + case final XDirectory directory: + debugPrint('\tDirectory: ${directory.uri}'); + } + } + } else { + debugPrint('No directory selected.'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Open a File'), + backgroundColor: Colors.blue, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open File'), + onPressed: () => _openFile(context), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open Directory'), + onPressed: () => _openDirectory(), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Default Constructor. + const TextDisplay({ + super.key, + required this.filename, + required this.fileContents, + }); + + /// The name of the file. + final String filename; + + /// The contents of the file. + final String fileContents; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(filename), + content: Scrollbar( + child: SingleChildScrollView(child: Text(fileContents)), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/cross_file/cross_file_android/example/pubspec.yaml b/packages/cross_file/cross_file_android/example/pubspec.yaml new file mode 100644 index 000000000000..1c12ae4314ba --- /dev/null +++ b/packages/cross_file/cross_file_android/example/pubspec.yaml @@ -0,0 +1,38 @@ +name: cross_file_android_example +description: "Demonstrates how to use the cross_file_android plugin." +publish_to: 'none' + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +dependencies: + file_selector: + git: + url: https://github.com/bparrishMines/packages + ref: new_file_selector + path: packages/file_selector/file_selector + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + mime: ^2.0.0 + video_player: ^2.8.6 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + +dependency_overrides: + # When depending on this package from a real application you should use: + # cross_file_android: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + cross_file_android: + path: ../ diff --git a/packages/cross_file/cross_file_android/lib/cross_file_android.dart b/packages/cross_file/cross_file_android/lib/cross_file_android.dart new file mode 100644 index 000000000000..dbf0a9f3f0b0 --- /dev/null +++ b/packages/cross_file/cross_file_android/lib/cross_file_android.dart @@ -0,0 +1,13 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart' + show + CrossFilePlatform, + ListParams, + PlatformScopedStorageXDirectoryCreationParams, + PlatformScopedStorageXFileCreationParams, + PlatformXFileEntity; + +export 'src/cross_file_android.dart'; diff --git a/packages/cross_file/cross_file_android/lib/src/android_library.g.dart b/packages/cross_file/cross_file_android/lib/src/android_library.g.dart new file mode 100644 index 000000000000..1e2bf3e55127 --- /dev/null +++ b/packages/cross_file/cross_file_android/lib/src/android_library.g.dart @@ -0,0 +1,1343 @@ +// 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. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:io' show Platform; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' + show ReadBuffer, WriteBuffer, immutable, protected, visibleForTesting; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart' show WidgetsFlutterBinding; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse({ + Object? result, + PlatformException? error, + bool empty = false, +}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +/// Provides overrides for the constructors and static members of each +/// Dart proxy class. +/// +/// This is only intended to be used with unit tests to prevent errors from +/// making message calls in a unit test. +/// +/// See [PigeonOverrides.pigeon_reset] to set all overrides back to null. +@visibleForTesting +class PigeonOverrides { + /// Overrides [DocumentFile.fromSingleUri]. + static DocumentFile Function({required String singleUri})? + documentFile_fromSingleUri; + + /// Overrides [DocumentFile.fromTreeUri]. + static DocumentFile Function({required String treeUri})? + documentFile_fromTreeUri; + + /// Overrides [ContentResolver.instance]. + static ContentResolver? contentResolver_instance; + + /// Sets all overridden ProxyApi class members to null. + static void pigeon_reset() { + documentFile_fromSingleUri = null; + documentFile_fromTreeUri = null; + contentResolver_instance = null; + } +} + +/// An immutable object that serves as the base class for all Dart proxy classes +/// and can provide functional copies of itself. +/// +/// All implementers are expected to be [immutable] as defined by the annotation +/// and override [pigeon_copy] returning an instance of itself. +@immutable +abstract class PigeonInternalProxyApiBaseClass { + /// Construct a [PigeonInternalProxyApiBaseClass]. + PigeonInternalProxyApiBaseClass({ + this.pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) : pigeon_instanceManager = + pigeon_instanceManager ?? PigeonInstanceManager.instance; + + /// Sends and receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used, which routes to + /// the host platform. + @protected + final BinaryMessenger? pigeon_binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final PigeonInstanceManager pigeon_instanceManager; + + /// Instantiates and returns a functionally identical object to oneself. + /// + /// Outside of tests, this method should only ever be called by + /// [PigeonInstanceManager]. + /// + /// Subclasses should always override their parent's implementation of this + /// method. + @protected + PigeonInternalProxyApiBaseClass pigeon_copy(); +} + +/// Maintains instances used to communicate with the native objects they +/// represent. +/// +/// Added instances are stored as weak references and their copies are stored +/// as strong references to maintain access to their variables and callback +/// methods. Both are stored with the same identifier. +/// +/// When a weak referenced instance becomes inaccessible, +/// [onWeakReferenceRemoved] is called with its associated identifier. +/// +/// If an instance is retrieved and has the possibility to be used, +/// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference +/// is added as a weak reference with the same identifier. This prevents a +/// scenario where the weak referenced instance was released and then later +/// returned by the host platform. +class PigeonInstanceManager { + /// Constructs a [PigeonInstanceManager]. + PigeonInstanceManager({required void Function(int) onWeakReferenceRemoved}) { + this.onWeakReferenceRemoved = (int identifier) { + _weakInstances.remove(identifier); + onWeakReferenceRemoved(identifier); + }; + _finalizer = Finalizer(this.onWeakReferenceRemoved); + } + + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously by the host platform. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + static const int _maxDartCreatedIdentifier = 65536; + + /// The default [PigeonInstanceManager] used by Dart proxy classes. + /// + /// On creation, this manager makes a call to clear the native + /// InstanceManager. This is to prevent identifier conflicts after a host + /// restart. + static final PigeonInstanceManager instance = _initInstance(); + + // Expando is used because it doesn't prevent its keys from becoming + // inaccessible. This allows the manager to efficiently retrieve an identifier + // of an instance without holding a strong reference to that instance. + // + // It also doesn't use `==` to search for identifiers, which would lead to an + // infinite loop when comparing an object to its copy. (i.e. which was caused + // by calling instanceManager.getIdentifier() inside of `==` while this was a + // HashMap). + final Expando _identifiers = Expando(); + final Map> + _weakInstances = >{}; + final Map _strongInstances = + {}; + late final Finalizer _finalizer; + int _nextIdentifier = 0; + + /// Called when a weak referenced instance is removed by [removeWeakReference] + /// or becomes inaccessible. + late final void Function(int) onWeakReferenceRemoved; + + static PigeonInstanceManager _initInstance() { + if (Platform.environment['FLUTTER_TEST'] == 'true') { + return PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + } + WidgetsFlutterBinding.ensureInitialized(); + final _PigeonInternalInstanceManagerApi api = + _PigeonInternalInstanceManagerApi(); + // Clears the native `PigeonInstanceManager` on the initial use of the Dart one. + api.clear(); + final PigeonInstanceManager instanceManager = PigeonInstanceManager( + onWeakReferenceRemoved: (int identifier) { + api.removeStrongReference(identifier); + }, + ); + _PigeonInternalInstanceManagerApi.setUpMessageHandlers( + instanceManager: instanceManager, + ); + DocumentFile.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + ContentResolver.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + InputStream.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + return instanceManager; + } + + /// Adds a new instance that was instantiated by Dart. + /// + /// In other words, Dart wants to add a new instance that will represent + /// an object that will be instantiated on the host platform. + /// + /// Throws assertion error if the instance has already been added. + /// + /// Returns the randomly generated id of the [instance] added. + int addDartCreatedInstance(PigeonInternalProxyApiBaseClass instance) { + assert(getIdentifier(instance) == null); + + final int identifier = _nextUniqueIdentifier(); + _identifiers[instance] = identifier; + _weakInstances[identifier] = WeakReference( + instance, + ); + _finalizer.attach(instance, identifier, detach: instance); + + final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + return identifier; + } + + /// Removes the instance, if present, and call [onWeakReferenceRemoved] with + /// its identifier. + /// + /// Returns the identifier associated with the removed instance. Otherwise, + /// `null` if the instance was not found in this manager. + /// + /// This does not remove the strong referenced instance associated with + /// [instance]. This can be done with [remove]. + int? removeWeakReference(PigeonInternalProxyApiBaseClass instance) { + final int? identifier = getIdentifier(instance); + if (identifier == null) { + return null; + } + + _identifiers[instance] = null; + _finalizer.detach(instance); + onWeakReferenceRemoved(identifier); + + return identifier; + } + + /// Removes [identifier] and its associated strongly referenced instance, if + /// present, from the manager. + /// + /// Returns the strong referenced instance associated with [identifier] before + /// it was removed. Returns `null` if [identifier] was not associated with + /// any strong reference. + /// + /// Throws an `AssertionError` if the weak referenced instance associated with + /// [identifier] is not removed first. This can be done with + /// [removeWeakReference]. + T? remove(int identifier) { + final T? instance = _weakInstances[identifier]?.target as T?; + assert( + instance == null, + 'A strong instance with identifier $identifier is being removed despite the weak reference still existing: $instance', + ); + return _strongInstances.remove(identifier) as T?; + } + + /// Retrieves the instance associated with identifier. + /// + /// The value returned is chosen from the following order: + /// + /// 1. A weakly referenced instance associated with identifier. + /// 2. If the only instance associated with identifier is a strongly + /// referenced instance, a copy of the instance is added as a weak reference + /// with the same identifier. Returning the newly created copy. + /// 3. If no instance is associated with identifier, returns null. + /// + /// This method also expects the host `InstanceManager` to have a strong + /// reference to the instance the identifier is associated with. + T? getInstanceWithWeakReference( + int identifier, + ) { + final PigeonInternalProxyApiBaseClass? weakInstance = + _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final PigeonInternalProxyApiBaseClass? strongInstance = + _strongInstances[identifier]; + if (strongInstance != null) { + final PigeonInternalProxyApiBaseClass copy = strongInstance + .pigeon_copy(); + _identifiers[copy] = identifier; + _weakInstances[identifier] = + WeakReference(copy); + _finalizer.attach(copy, identifier, detach: copy); + return copy as T; + } + return strongInstance as T?; + } + + return weakInstance as T; + } + + /// Retrieves the identifier associated with instance. + int? getIdentifier(PigeonInternalProxyApiBaseClass instance) { + return _identifiers[instance]; + } + + /// Adds a new instance that was instantiated by the host platform. + /// + /// In other words, the host platform wants to add a new instance that + /// represents an object on the host platform. Stored with [identifier]. + /// + /// Throws assertion error if the instance or its identifier has already been + /// added. + void addHostCreatedInstance( + PigeonInternalProxyApiBaseClass instance, + int identifier, + ) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + + _identifiers[instance] = identifier; + _strongInstances[identifier] = instance; + } + + /// Whether this manager contains the given [identifier]. + bool containsIdentifier(int identifier) { + return _weakInstances.containsKey(identifier) || + _strongInstances.containsKey(identifier); + } + + int _nextUniqueIdentifier() { + late int identifier; + do { + identifier = _nextIdentifier; + _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; + } while (containsIdentifier(identifier)); + return identifier; + } +} + +/// Generated API for managing the Dart and native `PigeonInstanceManager`s. +class _PigeonInternalInstanceManagerApi { + /// Constructor for [_PigeonInternalInstanceManagerApi]. + _PigeonInternalInstanceManagerApi({BinaryMessenger? binaryMessenger}) + : pigeonVar_binaryMessenger = binaryMessenger; + + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + static void setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? binaryMessenger, + PigeonInstanceManager? instanceManager, + }) { + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference was null.', + ); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert( + arg_identifier != null, + 'Argument for dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference was null, expected non-null int.', + ); + try { + (instanceManager ?? PigeonInstanceManager.instance).remove( + arg_identifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + Future removeStrongReference(int identifier) async { + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.removeStrongReference'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [identifier], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Clear the native `PigeonInstanceManager`. + /// + /// This is typically called after a hot restart. + Future clear() async { + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.PigeonInternalInstanceManager.clear'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} + +class _PigeonInternalProxyApiBaseCodec extends _PigeonCodec { + const _PigeonInternalProxyApiBaseCodec(this.instanceManager); + final PigeonInstanceManager instanceManager; + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PigeonInternalProxyApiBaseClass) { + buffer.putUint8(128); + writeValue(buffer, instanceManager.getIdentifier(value)); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return instanceManager.getInstanceWithWeakReference( + readValue(buffer)! as int, + ); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Representation of a document backed by either a +/// android.provider.DocumentsProvider or a raw file on disk. +/// +/// See https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile. +class DocumentFile extends PigeonInternalProxyApiBaseClass { + /// Create a DocumentFile representing the single document at the given Uri. + factory DocumentFile.fromSingleUri({ + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + required String singleUri, + }) { + if (PigeonOverrides.documentFile_fromSingleUri != null) { + return PigeonOverrides.documentFile_fromSingleUri!(singleUri: singleUri); + } + return DocumentFile.pigeon_fromSingleUri( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + singleUri: singleUri, + ); + } + + /// Create a DocumentFile representing the single document at the given Uri. + @protected + DocumentFile.pigeon_fromSingleUri({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required String singleUri, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.fromSingleUri'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier, singleUri], + ); + () async { + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Create a DocumentFile representing the document tree rooted at the given + /// Uri. + factory DocumentFile.fromTreeUri({ + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + required String treeUri, + }) { + if (PigeonOverrides.documentFile_fromTreeUri != null) { + return PigeonOverrides.documentFile_fromTreeUri!(treeUri: treeUri); + } + return DocumentFile.pigeon_fromTreeUri( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + treeUri: treeUri, + ); + } + + /// Create a DocumentFile representing the document tree rooted at the given + /// Uri. + @protected + DocumentFile.pigeon_fromTreeUri({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required String treeUri, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.fromTreeUri'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier, treeUri], + ); + () async { + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [DocumentFile] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + DocumentFile.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecDocumentFile = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + DocumentFile Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.cross_file_android.DocumentFile.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.cross_file_android.DocumentFile.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.cross_file_android.DocumentFile.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + DocumentFile.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Indicates whether the current context is allowed to read from this file. + Future canRead() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.canRead'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Deletes this file. + Future delete() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.delete'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Returns a boolean indicating whether this file can be found. + Future exists() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.exists'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Returns the time when this file was last modified, measured in + /// milliseconds since January 1st, 1970, midnight. + Future lastModified() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.lastModified'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as int?)!; + } + } + + /// Returns the length of this file in bytes. + Future length() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.length'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as int?)!; + } + } + + /// Indicates if this file represents a *file*. + Future isFile() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.isFile'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Indicates if this file represents a *directory*. + Future isDirectory() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.isDirectory'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Returns an list of files contained in the directory represented by this + /// file. + Future> listFiles() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.listFiles'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)!.cast(); + } + } + + /// A Uri for the underlying document represented by this file. + Future getUri() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.getUri'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// Returns the display name of this document. + Future getName() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDocumentFile; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.DocumentFile.getName'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + @override + DocumentFile pigeon_copy() { + return DocumentFile.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// This class provides applications access to the content model. +/// +/// See https://developer.android.com/reference/kotlin/android/content/ContentResolver +class ContentResolver extends PigeonInternalProxyApiBaseClass { + /// Constructs [ContentResolver] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ContentResolver.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecContentResolver = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// Helper field for accessing the `ContentResolver` from the current Android + /// `Context`. + static final ContentResolver _instance = pigeonVar_instance(); + + /// Helper field for accessing the `ContentResolver` from the current Android + /// `Context`. + static ContentResolver get instance => + PigeonOverrides.contentResolver_instance ?? _instance; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ContentResolver Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.cross_file_android.ContentResolver.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.cross_file_android.ContentResolver.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.cross_file_android.ContentResolver.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ContentResolver.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static ContentResolver pigeonVar_instance() { + final ContentResolver pigeonVar_instance = + ContentResolver.pigeon_detached(); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger pigeonVar_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int pigeonVar_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(pigeonVar_instance); + () async { + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.ContentResolver.instance'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + return pigeonVar_instance; + } + + /// Open a stream on to the content associated with a content URI. + Future openInputStream(String uri) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecContentResolver; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.ContentResolver.openInputStream'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, uri], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as InputStream?); + } + } + + @override + ContentResolver pigeon_copy() { + return ContentResolver.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// This abstract class is the superclass of all classes representing an input +/// stream of bytes. +/// +/// See https://developer.android.com/reference/java/io/InputStream. +class InputStream extends PigeonInternalProxyApiBaseClass { + /// Constructs [InputStream] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + InputStream.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecInputStream = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + InputStream Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.cross_file_android.InputStream.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.cross_file_android.InputStream.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.cross_file_android.InputStream.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + InputStream.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Reads some number of bytes from the input stream and stores them into the + /// returns them. + Future readBytes(int len) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecInputStream; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.InputStream.readBytes'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, len], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?)!; + } + } + + /// Reads all remaining bytes from the input stream. + Future readAllBytes() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecInputStream; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.InputStream.readAllBytes'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?)!; + } + } + + /// Skips over and discards n bytes of data from this input stream. + Future skip(int n) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecInputStream; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_android.InputStream.skip'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, n], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as int?)!; + } + } + + @override + InputStream pigeon_copy() { + return InputStream.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} diff --git a/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_directory.dart b/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_directory.dart new file mode 100644 index 000000000000..ef0d2363e578 --- /dev/null +++ b/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_directory.dart @@ -0,0 +1,40 @@ +// 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 'android_library.g.dart'; +import 'android_scoped_storage_cross_file.dart'; + +/// Implementation of [PlatformScopedStorageXDirectory] for iOS and macOS. +base class AndroidScopedStorageXDirectory + extends PlatformScopedStorageXDirectory { + /// Constructs an [AndroidScopedStorageXDirectory]. + AndroidScopedStorageXDirectory(super.params) : super.implementation(); + + late final DocumentFile _documentFile = DocumentFile.fromTreeUri( + treeUri: params.uri, + ); + + @override + Future exists() async { + return await _documentFile.exists() && await _documentFile.isDirectory(); + } + + @override + Stream list(ListParams params) async* { + for (final DocumentFile documentFile in await _documentFile.listFiles()) { + final String uri = await documentFile.getUri(); + if (await documentFile.isFile()) { + yield AndroidScopedStorageXFile( + PlatformScopedStorageXFileCreationParams(uri: uri), + ); + } else if (await documentFile.isDirectory()) { + yield AndroidScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams(uri: uri), + ); + } + } + } +} diff --git a/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_file.dart b/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_file.dart new file mode 100644 index 000000000000..6f32c7e24bcd --- /dev/null +++ b/packages/cross_file/cross_file_android/lib/src/android_scoped_storage_cross_file.dart @@ -0,0 +1,116 @@ +// 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:math'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'android_library.g.dart' as android; + +/// Implementation of [PlatformScopedStorageXFile] for Android. +base class AndroidScopedStorageXFile extends PlatformScopedStorageXFile { + /// Constructs an [AndroidScopedStorageXFile]. + AndroidScopedStorageXFile(super.params) : super.implementation(); + + late final android.DocumentFile _documentFile = + android.DocumentFile.fromSingleUri(singleUri: params.uri); + + late final android.ContentResolver _contentResolver = + android.ContentResolver.instance; + + /// Maximum number of bytes to read at a time from the native Android + /// InputStream. + /// + /// Only visible for testing. + @visibleForTesting + static const int maxByteArrayLen = 4 * 1024; + + @override + Future lastModified() async { + final int msSinceEpoch = await _documentFile.lastModified(); + if (msSinceEpoch == 0) { + return null; + } + + return DateTime.fromMillisecondsSinceEpoch(msSinceEpoch); + } + + @override + Future length() async { + final int length = await _documentFile.length(); + if (length == 0) { + return null; + } + + return length; + } + + @override + Stream openRead([int? start, int? end]) async* { + final int? fileLength = await length(); + if (fileLength == null) { + throw UnsupportedError('Cannot access file length.'); + } + + int bytesToRead = (end ?? fileLength) - (start ?? 0); + assert(bytesToRead >= 0); + + final android.InputStream? inputStream = await _contentResolver + .openInputStream(params.uri); + + if (inputStream case final android.InputStream inputStream) { + if (start != null && start > 0) { + await inputStream.skip(start); + } + + late Uint8List bytes; + do { + bytes = await inputStream.readBytes(min(bytesToRead, maxByteArrayLen)); + yield bytes; + bytesToRead -= bytes.length; + } while (bytesToRead > 0 && bytes.isNotEmpty); + } else { + throw NullInputStreamError(params.uri); + } + } + + @override + Future readAsBytes() async { + final android.InputStream? inputStream = await _contentResolver + .openInputStream(params.uri); + if (inputStream case final android.InputStream inputStream) { + return inputStream.readAllBytes(); + } + + throw NullInputStreamError(params.uri); + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return encoding.decodeStream(openRead()); + } + + @override + Future canRead() => _documentFile.canRead(); + + @override + Future exists() async { + return await _documentFile.exists() && await _documentFile.isFile(); + } + + @override + Future name() => _documentFile.getName(); +} + +/// Error thrown when the native [android.InputStream] is not accessible. +class NullInputStreamError extends UnsupportedError { + /// Constructs a [NullInputStreamError]. + NullInputStreamError(String uri) + : super( + 'Failed to get native InputStream from file with path: $uri. ' + 'App may not have permissions to access file.', + ); +} diff --git a/packages/cross_file/cross_file_android/lib/src/cross_file_android.dart b/packages/cross_file/cross_file_android/lib/src/cross_file_android.dart new file mode 100644 index 000000000000..b3b6c47a5017 --- /dev/null +++ b/packages/cross_file/cross_file_android/lib/src/cross_file_android.dart @@ -0,0 +1,31 @@ +// 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_io/cross_file_io.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; + +import 'android_scoped_storage_cross_directory.dart'; +import 'android_scoped_storage_cross_file.dart'; + +/// Implementation of [CrossFilePlatform] for Android. +base class CrossFileAndroid extends CrossFileIO { + /// Registers this class as the default instance of [CrossFilePlatform]. + static void registerWith() { + CrossFilePlatform.instance = CrossFileAndroid(); + } + + @override + AndroidScopedStorageXFile createPlatformScopedStorageXFile( + PlatformScopedStorageXFileCreationParams params, + ) { + return AndroidScopedStorageXFile(params); + } + + @override + AndroidScopedStorageXDirectory createPlatformScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams params, + ) { + return AndroidScopedStorageXDirectory(params); + } +} diff --git a/packages/cross_file/cross_file_android/pigeons/android_library.dart b/packages/cross_file/cross_file_android/pigeons/android_library.dart new file mode 100644 index 000000000000..2070a2250d6a --- /dev/null +++ b/packages/cross_file/cross_file_android/pigeons/android_library.dart @@ -0,0 +1,105 @@ +// 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. + +// ignore_for_file: avoid_unused_constructor_parameters + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/android_library.g.dart', + kotlinOut: + 'android/src/main/kotlin/dev/flutter/packages/cross_file_android/proxies/AndroidLibrary.g.kt', + kotlinOptions: KotlinOptions( + package: 'dev.flutter.packages.cross_file_android.proxies', + ), + copyrightHeader: 'pigeons/copyright.txt', + ), +) +/// Representation of a document backed by either a +/// android.provider.DocumentsProvider or a raw file on disk. +/// +/// See https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.documentfile.provider.DocumentFile', + ), +) +abstract class DocumentFile { + /// Create a DocumentFile representing the single document at the given Uri. + DocumentFile.fromSingleUri(String singleUri); + + /// Create a DocumentFile representing the document tree rooted at the given + /// Uri. + DocumentFile.fromTreeUri(String treeUri); + + /// Indicates whether the current context is allowed to read from this file. + bool canRead(); + + /// Deletes this file. + bool delete(); + + /// Returns a boolean indicating whether this file can be found. + bool exists(); + + /// Returns the time when this file was last modified, measured in + /// milliseconds since January 1st, 1970, midnight. + int lastModified(); + + /// Returns the length of this file in bytes. + int length(); + + /// Indicates if this file represents a *file*. + bool isFile(); + + /// Indicates if this file represents a *directory*. + bool isDirectory(); + + /// Returns an list of files contained in the directory represented by this + /// file. + List listFiles(); + + /// A Uri for the underlying document represented by this file. + String getUri(); + + /// Returns the display name of this document. + String? getName(); +} + +/// This class provides applications access to the content model. +/// +/// See https://developer.android.com/reference/kotlin/android/content/ContentResolver +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.content.ContentResolver', + ), +) +abstract class ContentResolver { + /// Helper field for accessing the `ContentResolver` from the current Android + /// `Context`. + @static + late final ContentResolver instance; + + /// Open a stream on to the content associated with a content URI. + InputStream? openInputStream(String uri); +} + +/// This abstract class is the superclass of all classes representing an input +/// stream of bytes. +/// +/// See https://developer.android.com/reference/java/io/InputStream. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions(fullClassName: 'java.io.InputStream'), +) +abstract class InputStream { + /// Reads some number of bytes from the input stream and stores them into the + /// returns them. + Uint8List readBytes(int len); + + /// Reads all remaining bytes from the input stream. + Uint8List readAllBytes(); + + /// Skips over and discards n bytes of data from this input stream. + int skip(int n); +} diff --git a/packages/cross_file/cross_file_android/pigeons/copyright.txt b/packages/cross_file/cross_file_android/pigeons/copyright.txt new file mode 100644 index 000000000000..07e5f8598a80 --- /dev/null +++ b/packages/cross_file/cross_file_android/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. diff --git a/packages/cross_file/cross_file_android/pubspec.yaml b/packages/cross_file/cross_file_android/pubspec.yaml new file mode 100644 index 000000000000..c39a8131ab93 --- /dev/null +++ b/packages/cross_file/cross_file_android/pubspec.yaml @@ -0,0 +1,43 @@ +name: cross_file_android +description: Implementation of cross_file_platform_interface for Android. +repository: https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_android +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 +version: 0.0.1 + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +flutter: + plugin: + implements: cross_file + platforms: + android: + dartPluginClass: CrossFileAndroid + package: dev.flutter.packages.cross_file_android + pluginClass: CrossFileAndroidPlugin + +dependencies: + cross_file_io: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_io + cross_file_platform_interface: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_platform_interface + flutter: + sdk: flutter + +dev_dependencies: + build_runner: ^2.10.4 + flutter_test: + sdk: flutter + mockito: ^5.4.4 + pigeon: ^26.1.4 + +topics: + - file + - cross-file diff --git a/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.dart b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.dart new file mode 100644 index 000000000000..9d8059f5de82 --- /dev/null +++ b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.dart @@ -0,0 +1,75 @@ +// 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_android/src/android_library.g.dart' as android; +import 'package:cross_file_android/src/android_scoped_storage_cross_directory.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'android_scoped_storage_cross_directory_test.mocks.dart'; + +@GenerateMocks([android.DocumentFile]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + android.PigeonOverrides.pigeon_reset(); + }); + + test('exists', () async { + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.exists()).thenAnswer((_) async => true); + when(mockDocumentFile.isDirectory()).thenAnswer((_) async => true); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromTreeUri = + ({required String treeUri}) { + expect(treeUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams(uri: uri), + ); + + expect(await file.exists(), true); + }); + + test('list', () async { + final mockFile = MockDocumentFile(); + const fileUri = 'fileUri'; + when(mockFile.getUri()).thenAnswer((_) async => fileUri); + when(mockFile.isFile()).thenAnswer((_) async => true); + final files = [mockFile]; + + final mockDirectory = MockDocumentFile(); + when(mockDirectory.listFiles()).thenAnswer((_) async => files); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromTreeUri = + ({required String treeUri}) { + expect(treeUri, uri); + return mockDirectory; + }; + + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, fileUri); + return mockFile; + }; + + final dir = AndroidScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams(uri: uri), + ); + + final List entityUris = await dir + .list(ListParams()) + .map((PlatformXFileEntity entity) => entity.params.uri) + .toList(); + + expect(entityUris, [fileUri]); + }); +} diff --git a/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.mocks.dart b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.mocks.dart new file mode 100644 index 000000000000..f4bac06e92f6 --- /dev/null +++ b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_directory_test.mocks.dart @@ -0,0 +1,151 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cross_file_android/test/android_scoped_storage_cross_directory_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:cross_file_android/src/android_library.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDocumentFile_1 extends _i1.SmartFake implements _i2.DocumentFile { + _FakeDocumentFile_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [DocumentFile]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDocumentFile extends _i1.Mock implements _i2.DocumentFile { + MockDocumentFile() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future canRead() => + (super.noSuchMethod( + Invocation.method(#canRead, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future delete() => + (super.noSuchMethod( + Invocation.method(#delete, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future exists() => + (super.noSuchMethod( + Invocation.method(#exists, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future lastModified() => + (super.noSuchMethod( + Invocation.method(#lastModified, []), + returnValue: _i3.Future.value(0), + ) + as _i3.Future); + + @override + _i3.Future length() => + (super.noSuchMethod( + Invocation.method(#length, []), + returnValue: _i3.Future.value(0), + ) + as _i3.Future); + + @override + _i3.Future isFile() => + (super.noSuchMethod( + Invocation.method(#isFile, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future isDirectory() => + (super.noSuchMethod( + Invocation.method(#isDirectory, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future> listFiles() => + (super.noSuchMethod( + Invocation.method(#listFiles, []), + returnValue: _i3.Future>.value( + <_i2.DocumentFile>[], + ), + ) + as _i3.Future>); + + @override + _i3.Future getUri() => + (super.noSuchMethod( + Invocation.method(#getUri, []), + returnValue: _i3.Future.value( + _i4.dummyValue(this, Invocation.method(#getUri, [])), + ), + ) + as _i3.Future); + + @override + _i3.Future getName() => + (super.noSuchMethod( + Invocation.method(#getName, []), + returnValue: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.DocumentFile pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeDocumentFile_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.DocumentFile); +} diff --git a/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.dart b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.dart new file mode 100644 index 000000000000..8eb575acab8b --- /dev/null +++ b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.dart @@ -0,0 +1,318 @@ +// 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:cross_file_android/src/android_library.g.dart' as android; +import 'package:cross_file_android/src/android_scoped_storage_cross_file.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'android_scoped_storage_cross_file_test.mocks.dart'; + +@GenerateMocks([ + android.ContentResolver, + android.DocumentFile, + android.InputStream, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + android.PigeonOverrides.pigeon_reset(); + }); + + test('lastModified', () async { + final mockDocumentFile = MockDocumentFile(); + const lastModified = 123; + when(mockDocumentFile.lastModified()).thenAnswer((_) async => lastModified); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect( + await file.lastModified(), + DateTime.fromMillisecondsSinceEpoch(lastModified), + ); + }); + + test('length', () async { + final mockDocumentFile = MockDocumentFile(); + const length = 123; + when(mockDocumentFile.length()).thenAnswer((_) async => length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.length(), length); + }); + + group('openRead', () { + test('openRead finishes successfully', () async { + final testBytes = Uint8List.fromList([0, 1, 2]); + + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.length()).thenAnswer((_) async => testBytes.length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final mockInputStream = MockInputStream(); + setUpInputStreamWithBytes(mockInputStream, testBytes); + + final mockContentResolver = MockContentResolver(); + when( + mockContentResolver.openInputStream(uri), + ).thenAnswer((_) async => mockInputStream); + android.PigeonOverrides.contentResolver_instance = mockContentResolver; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(combineLists(await file.openRead().toList()), testBytes); + }); + + test( + 'openRead finishes successfully with file larger than max array len', + () async { + final testBytes = Uint8List.fromList( + List.filled(AndroidScopedStorageXFile.maxByteArrayLen + 1, 0), + ); + + final mockDocumentFile = MockDocumentFile(); + when( + mockDocumentFile.length(), + ).thenAnswer((_) async => testBytes.length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final mockInputStream = MockInputStream(); + setUpInputStreamWithBytes(mockInputStream, testBytes); + + final mockContentResolver = MockContentResolver(); + when( + mockContentResolver.openInputStream(uri), + ).thenAnswer((_) async => mockInputStream); + android.PigeonOverrides.contentResolver_instance = mockContentResolver; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(combineLists(await file.openRead().toList()), testBytes); + }, + ); + + test('openRead finishes successfully with subset of array', () async { + final testBytes = Uint8List.fromList([0, 0, 0, 1, 1, 1, 0, 0, 0]); + + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.length()).thenAnswer((_) async => testBytes.length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final mockInputStream = MockInputStream(); + setUpInputStreamWithBytes(mockInputStream, testBytes); + + final mockContentResolver = MockContentResolver(); + when( + mockContentResolver.openInputStream(uri), + ).thenAnswer((_) async => mockInputStream); + android.PigeonOverrides.contentResolver_instance = mockContentResolver; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(combineLists(await file.openRead(3, 6).toList()), [1, 1, 1]); + }); + }); + + test('readAsBytes', () async { + final testBytes = Uint8List.fromList([0, 1, 2]); + + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.length()).thenAnswer((_) async => testBytes.length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final mockInputStream = MockInputStream(); + when(mockInputStream.readAllBytes()).thenAnswer((_) async => testBytes); + + final mockContentResolver = MockContentResolver(); + when( + mockContentResolver.openInputStream(uri), + ).thenAnswer((_) async => mockInputStream); + android.PigeonOverrides.contentResolver_instance = mockContentResolver; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.readAsBytes(), testBytes); + }); + + test('readAsString', () async { + const testString = 'Hello, World!'; + final Uint8List testBytes = utf8.encode(testString); + + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.length()).thenAnswer((_) async => testBytes.length); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final mockInputStream = MockInputStream(); + setUpInputStreamWithBytes(mockInputStream, testBytes); + + final mockContentResolver = MockContentResolver(); + when( + mockContentResolver.openInputStream(uri), + ).thenAnswer((_) async => mockInputStream); + android.PigeonOverrides.contentResolver_instance = mockContentResolver; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.readAsString(), testString); + }); + + test('canRead', () async { + final mockDocumentFile = MockDocumentFile(); + const canRead = false; + when(mockDocumentFile.canRead()).thenAnswer((_) async => canRead); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.canRead(), canRead); + }); + + test('exists', () async { + final mockDocumentFile = MockDocumentFile(); + when(mockDocumentFile.exists()).thenAnswer((_) async => true); + when(mockDocumentFile.isFile()).thenAnswer((_) async => true); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.exists(), true); + }); + + test('name', () async { + final mockDocumentFile = MockDocumentFile(); + const name = 'name'; + when(mockDocumentFile.getName()).thenAnswer((_) async => name); + + const uri = 'uri'; + android.PigeonOverrides.documentFile_fromSingleUri = + ({required String singleUri}) { + expect(singleUri, uri); + return mockDocumentFile; + }; + + final file = AndroidScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: uri), + ); + + expect(await file.name(), name); + }); +} + +void setUpInputStreamWithBytes( + MockInputStream mockInputStream, + Uint8List bytes, +) { + Iterable remainingBytes = bytes.toList(); + + when(mockInputStream.skip(any)).thenAnswer((Invocation invocation) async { + final amount = invocation.positionalArguments[0] as int; + if (amount < 0) { + return 0; + } + + final Iterable newRemainingBytes = remainingBytes.skip(amount); + + final int diff = remainingBytes.length - newRemainingBytes.length; + remainingBytes = newRemainingBytes; + return diff; + }); + + when(mockInputStream.readBytes(any)).thenAnswer(( + Invocation invocation, + ) async { + final len = invocation.positionalArguments[0] as int; + + final List bytesRead = remainingBytes.take(len).toList(); + remainingBytes = remainingBytes.skip(len); + + return Uint8List.fromList(bytesRead); + }); +} + +Uint8List combineLists(List lists) { + return Uint8List.fromList( + lists.expand((Uint8List element) => element).toList(), + ); +} diff --git a/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.mocks.dart b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.mocks.dart new file mode 100644 index 000000000000..8694dd65dee7 --- /dev/null +++ b/packages/cross_file/cross_file_android/test/android_scoped_storage_cross_file_test.mocks.dart @@ -0,0 +1,257 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cross_file_android/test/android_scoped_storage_cross_file_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:typed_data' as _i5; + +import 'package:cross_file_android/src/android_library.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeContentResolver_1 extends _i1.SmartFake + implements _i2.ContentResolver { + _FakeContentResolver_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDocumentFile_2 extends _i1.SmartFake implements _i2.DocumentFile { + _FakeDocumentFile_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeInputStream_3 extends _i1.SmartFake implements _i2.InputStream { + _FakeInputStream_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [ContentResolver]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockContentResolver extends _i1.Mock implements _i2.ContentResolver { + MockContentResolver() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future<_i2.InputStream?> openInputStream(String? uri) => + (super.noSuchMethod( + Invocation.method(#openInputStream, [uri]), + returnValue: _i3.Future<_i2.InputStream?>.value(), + ) + as _i3.Future<_i2.InputStream?>); + + @override + _i2.ContentResolver pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeContentResolver_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.ContentResolver); +} + +/// A class which mocks [DocumentFile]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDocumentFile extends _i1.Mock implements _i2.DocumentFile { + MockDocumentFile() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future canRead() => + (super.noSuchMethod( + Invocation.method(#canRead, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future delete() => + (super.noSuchMethod( + Invocation.method(#delete, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future exists() => + (super.noSuchMethod( + Invocation.method(#exists, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future lastModified() => + (super.noSuchMethod( + Invocation.method(#lastModified, []), + returnValue: _i3.Future.value(0), + ) + as _i3.Future); + + @override + _i3.Future length() => + (super.noSuchMethod( + Invocation.method(#length, []), + returnValue: _i3.Future.value(0), + ) + as _i3.Future); + + @override + _i3.Future isFile() => + (super.noSuchMethod( + Invocation.method(#isFile, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future isDirectory() => + (super.noSuchMethod( + Invocation.method(#isDirectory, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future> listFiles() => + (super.noSuchMethod( + Invocation.method(#listFiles, []), + returnValue: _i3.Future>.value( + <_i2.DocumentFile>[], + ), + ) + as _i3.Future>); + + @override + _i3.Future getUri() => + (super.noSuchMethod( + Invocation.method(#getUri, []), + returnValue: _i3.Future.value( + _i4.dummyValue(this, Invocation.method(#getUri, [])), + ), + ) + as _i3.Future); + + @override + _i3.Future getName() => + (super.noSuchMethod( + Invocation.method(#getName, []), + returnValue: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.DocumentFile pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeDocumentFile_2( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.DocumentFile); +} + +/// A class which mocks [InputStream]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockInputStream extends _i1.Mock implements _i2.InputStream { + MockInputStream() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future<_i5.Uint8List> readBytes(int? len) => + (super.noSuchMethod( + Invocation.method(#readBytes, [len]), + returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)), + ) + as _i3.Future<_i5.Uint8List>); + + @override + _i3.Future<_i5.Uint8List> readAllBytes() => + (super.noSuchMethod( + Invocation.method(#readAllBytes, []), + returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)), + ) + as _i3.Future<_i5.Uint8List>); + + @override + _i3.Future skip(int? n) => + (super.noSuchMethod( + Invocation.method(#skip, [n]), + returnValue: _i3.Future.value(0), + ) + as _i3.Future); + + @override + _i2.InputStream pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeInputStream_3( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.InputStream); +} diff --git a/packages/cross_file/cross_file_darwin/CHANGELOG.md b/packages/cross_file/cross_file_darwin/CHANGELOG.md new file mode 100644 index 000000000000..d0bd041d0ff6 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release. diff --git a/packages/cross_file/cross_file_darwin/LICENSE b/packages/cross_file/cross_file_darwin/LICENSE new file mode 100644 index 000000000000..29b709dac6c7 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/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_darwin/README.md b/packages/cross_file/cross_file_darwin/README.md new file mode 100644 index 000000000000..1c9e41b30df2 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/README.md @@ -0,0 +1,15 @@ +# cross\_file\_darwin + +The Darwin implementation of [`cross_file`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `cross_file` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +[1]: https://pub.dev/packages/cross_file +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/cross_file/cross_file_darwin/darwin/Tests/CrossFileDarwinApiTests.swift b/packages/cross_file/cross_file_darwin/darwin/Tests/CrossFileDarwinApiTests.swift new file mode 100644 index 000000000000..2cf1040bffdc --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/Tests/CrossFileDarwinApiTests.swift @@ -0,0 +1,51 @@ +// 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 Testing + +@testable import cross_file_darwin + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +struct CrossFileDarwinApiTests { + func createTempTestFile() throws -> URL { + let fileManager = FileManager.default + let tempDirectory = fileManager.temporaryDirectory + let fileName = UUID().uuidString + ".txt" + let fileURL = tempDirectory.appendingPathComponent(fileName) + + try "Hello, World!".write(to: fileURL, atomically: true, encoding: .utf8) + + return fileURL + } + + @Test func startAccessingSecurityScopedResource() throws { + let testFileURL = try createTempTestFile() + + let api = CrossFileDarwinApiImpl() + let canAccess = try api.startAccessingSecurityScopedResource(url: testFileURL.absoluteString) + + // Only returns true on iOS. + #if os(iOS) + #expect(canAccess) + #endif + + #expect(try String(contentsOf: testFileURL, encoding: .utf8) == "Hello, World!") + } + + @Test func tryCreateBookmarkedUrl() throws { + let testFileURL = try createTempTestFile() + + let api = CrossFileDarwinApiImpl() + let bookmarkedURLString = try! api.tryCreateBookmarkedUrl(url: testFileURL.absoluteString)! + + #expect(URL(fileURLWithPath: bookmarkedURLString) == testFileURL) + } +} diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin.podspec b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin.podspec new file mode 100644 index 000000000000..2b40a208f48a --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin.podspec @@ -0,0 +1,30 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# +Pod::Spec.new do |s| + s.name = 'cross_file_darwin' + s.version = '0.0.1' + s.summary = 'A cross platform file Plugin for Flutter.' + s.description = <<-DESC +A Flutter plugin that provides access to files. +Downloaded by pub (not CocoaPods). + DESC + s.homepage = 'https://github.com/flutter/packages' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_darwin' } + s.documentation_url = 'https://pub.dev/packages/cross_file' + s.source_files = 'cross_file_darwin/Sources/cross_file_darwin/**/*.swift' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + s.resource_bundles = {'cross_file_darwin_privacy' => ['cross_file_darwin/Sources/cross_file_darwin/Resources/PrivacyInfo.xcprivacy']} + s.ios.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } + s.swift_version = '5.0' +end diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Package.swift b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Package.swift new file mode 100644 index 000000000000..977be8ef52db --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 + +// 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 PackageDescription + +let package = Package( + name: "cross_file_darwin", + platforms: [ + .iOS("13.0"), + .macOS("10.15"), + ], + products: [ + .library(name: "cross-file-darwin", targets: ["cross_file_darwin"]) + ], + dependencies: [], + targets: [ + .target( + name: "cross_file_darwin", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinApis.g.swift b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinApis.g.swift new file mode 100644 index 000000000000..6718f7f13329 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinApis.g.swift @@ -0,0 +1,176 @@ +// 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. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private class CrossFileDarwinApisPigeonCodecReader: FlutterStandardReader { +} + +private class CrossFileDarwinApisPigeonCodecWriter: FlutterStandardWriter { +} + +private class CrossFileDarwinApisPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return CrossFileDarwinApisPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return CrossFileDarwinApisPigeonCodecWriter(data: data) + } +} + +class CrossFileDarwinApisPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = CrossFileDarwinApisPigeonCodec( + readerWriter: CrossFileDarwinApisPigeonCodecReaderWriter()) +} + +/// Api for getting access to file information. +/// +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol CrossFileDarwinApi { + /// Attempt to create a bookmarked URL that serves as a persistent reference + /// to a file. + func tryCreateBookmarkedUrl(url: String) throws -> String? + /// In an app that has adopted App Sandbox, makes the resource pointed to by a + /// security-scoped URL available to the app. + func startAccessingSecurityScopedResource(url: String) throws -> Bool + /// In an app that adopts App Sandbox, revokes access to the resource pointed + /// to by a security-scoped URL. + func stopAccessingSecurityScopedResource(url: String) throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class CrossFileDarwinApiSetup { + static var codec: FlutterStandardMessageCodec { CrossFileDarwinApisPigeonCodec.shared } + /// Sets up an instance of `CrossFileDarwinApi` to handle messages through the `binaryMessenger`. + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: CrossFileDarwinApi?, + messageChannelSuffix: String = "" + ) { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + /// Attempt to create a bookmarked URL that serves as a persistent reference + /// to a file. + let tryCreateBookmarkedUrlChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.tryCreateBookmarkedUrl\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + tryCreateBookmarkedUrlChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + do { + let result = try api.tryCreateBookmarkedUrl(url: urlArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + tryCreateBookmarkedUrlChannel.setMessageHandler(nil) + } + /// In an app that has adopted App Sandbox, makes the resource pointed to by a + /// security-scoped URL available to the app. + let startAccessingSecurityScopedResourceChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.startAccessingSecurityScopedResource\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + startAccessingSecurityScopedResourceChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + do { + let result = try api.startAccessingSecurityScopedResource(url: urlArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + startAccessingSecurityScopedResourceChannel.setMessageHandler(nil) + } + /// In an app that adopts App Sandbox, revokes access to the resource pointed + /// to by a security-scoped URL. + let stopAccessingSecurityScopedResourceChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.stopAccessingSecurityScopedResource\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + stopAccessingSecurityScopedResourceChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + do { + try api.stopAccessingSecurityScopedResource(url: urlArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + stopAccessingSecurityScopedResourceChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinPlugin.swift b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinPlugin.swift new file mode 100644 index 000000000000..cf1fdcb6f1ef --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinPlugin.swift @@ -0,0 +1,33 @@ +// 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. + +#if os(iOS) + import Flutter + import UIKit +#elseif os(macOS) + import Cocoa + import FlutterMacOS +#endif + +public class CrossFileDarwinPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + #if os(iOS) + let messenger = registrar.messenger() + #elseif os(macOS) + let messenger = registrar.messenger + #endif + + CrossFileDarwinApiSetup.setUp(binaryMessenger: messenger, api: CrossFileDarwinApiImpl()) + } + + public func detachFromEngine(for registrar: FlutterPluginRegistrar) { + #if os(iOS) + let messenger = registrar.messenger() + #elseif os(macOS) + let messenger = registrar.messenger + #endif + + CrossFileDarwinApiSetup.setUp(binaryMessenger: messenger, api: nil) + } +} diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/Resources/PrivacyInfo.xcprivacy b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000000..a34b7e2e60cc --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/apiImplementations/CrossFileDarwinApiImpl.swift b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/apiImplementations/CrossFileDarwinApiImpl.swift new file mode 100644 index 000000000000..f496d0706f5c --- /dev/null +++ b/packages/cross_file/cross_file_darwin/darwin/cross_file_darwin/Sources/cross_file_darwin/apiImplementations/CrossFileDarwinApiImpl.swift @@ -0,0 +1,42 @@ +// 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 Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Implementation of `CrossFileDarwinApi`. +class CrossFileDarwinApiImpl: CrossFileDarwinApi { + func tryCreateBookmarkedUrl(url: String) throws -> String? { + let nativeUrl = URL(string: url)! + let data = try nativeUrl.bookmarkData( + options: [], + includingResourceValuesForKeys: nil, + relativeTo: nil + ) + + var isStale: Bool = true + let bookmarkedUrl: URL = try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) + + if !isStale { + return bookmarkedUrl.absoluteString + } + + return nil + } + + func startAccessingSecurityScopedResource(url: String) throws -> Bool { + return URL(string: url)!.startAccessingSecurityScopedResource() + } + + func stopAccessingSecurityScopedResource(url: String) throws { + URL(string: url)!.stopAccessingSecurityScopedResource() + } +} diff --git a/packages/cross_file/cross_file_darwin/example/README.md b/packages/cross_file/cross_file_darwin/example/README.md new file mode 100644 index 000000000000..96b8bb17dbff --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/packages/cross_file/cross_file_darwin/example/assets/hello.txt b/packages/cross_file/cross_file_darwin/example/assets/hello.txt new file mode 100644 index 000000000000..b45ef6fec895 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/assets/hello.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/packages/cross_file/cross_file_darwin/example/integration_test/plugin_integration_test.dart b/packages/cross_file/cross_file_darwin/example/integration_test/plugin_integration_test.dart new file mode 100644 index 000000000000..621977be68c7 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,14 @@ +// 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_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // Since this test is lacking integration tests, this test ensures the example + // app can be launched on an emulator/device. + testWidgets('Launch Test', (WidgetTester tester) async {}); +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/Flutter/AppFrameworkInfo.plist b/packages/cross_file/cross_file_darwin/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000000..391a902b2beb --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Flutter/Debug.xcconfig b/packages/cross_file/cross_file_darwin/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..ec97fc6f3021 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/ios/Flutter/Release.xcconfig b/packages/cross_file/cross_file_darwin/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..c4855bfe2000 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/ios/Podfile b/packages/cross_file/cross_file_darwin/example/ios/Podfile new file mode 100644 index 000000000000..620e46eba607 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..3a080bfd2891 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,739 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0BBE3A77F94E5BCEF9021C5A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC6C9B7340FA95FE4267AE7F /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 87E2BFE282CB477A431CF7DF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9602D2B4619B7A716C4C80D1 /* Pods_RunnerTests.framework */; }; + 8F2171F72F21A5270014CE68 /* CrossFileDarwinApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2171F52F21A5270014CE68 /* CrossFileDarwinApiTests.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 26C8F5E7A3000CE8EAEBCE2D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 2F59C6F87154F8240EF20DCF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 3082B90ADC21503FE3B2C8E8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7CF818CFA5FB20A8AC1467CD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 8F2171F52F21A5270014CE68 /* CrossFileDarwinApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CrossFileDarwinApiTests.swift; path = ../../darwin/Tests/CrossFileDarwinApiTests.swift; sourceTree = SOURCE_ROOT; }; + 9602D2B4619B7A716C4C80D1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9D1864A39D740B0EAEC60CD5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B8A1290F753E9B94CB42B68A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + BC6C9B7340FA95FE4267AE7F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0BBE3A77F94E5BCEF9021C5A /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B320E567E4FD783B390DDCB2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 87E2BFE282CB477A431CF7DF /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 8F2171F52F21A5270014CE68 /* CrossFileDarwinApiTests.swift */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 6F98F75533BE3213F9C0BF8F /* Frameworks */ = { + isa = PBXGroup; + children = ( + BC6C9B7340FA95FE4267AE7F /* Pods_Runner.framework */, + 9602D2B4619B7A716C4C80D1 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + DB75EE80F48401E65664EBB7 /* Pods */, + 6F98F75533BE3213F9C0BF8F /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + DB75EE80F48401E65664EBB7 /* Pods */ = { + isa = PBXGroup; + children = ( + B8A1290F753E9B94CB42B68A /* Pods-Runner.debug.xcconfig */, + 7CF818CFA5FB20A8AC1467CD /* Pods-Runner.release.xcconfig */, + 26C8F5E7A3000CE8EAEBCE2D /* Pods-Runner.profile.xcconfig */, + 9D1864A39D740B0EAEC60CD5 /* Pods-RunnerTests.debug.xcconfig */, + 2F59C6F87154F8240EF20DCF /* Pods-RunnerTests.release.xcconfig */, + 3082B90ADC21503FE3B2C8E8 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + B10C59BD6BC2E3B30B458863 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + B320E567E4FD783B390DDCB2 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + E0527587EB0D03F15D75982F /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 9CD0C6A1D95F083343210EC4 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9CD0C6A1D95F083343210EC4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B10C59BD6BC2E3B30B458863 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E0527587EB0D03F15D75982F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + 8F2171F72F21A5270014CE68 /* CrossFileDarwinApiTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9D1864A39D740B0EAEC60CD5 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PT2QK76LGP; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2F59C6F87154F8240EF20DCF /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3082B90ADC21503FE3B2C8E8 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = PT2QK76LGP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..919434a6254f --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..e3773d42e24c --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/AppDelegate.swift b/packages/cross_file/cross_file_darwin/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000000..81eca8683601 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,20 @@ +// 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 Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..d36b1fab2d9d --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000000..dc9ada4725e9 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000000..7353c41ecf9c Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000000..797d452e4589 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000000..6ed2d933e112 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000000..4cd7b0099ca8 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000000..fe730945a01f Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000000..321773cd857a Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000000..797d452e4589 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000000..502f463a9bc8 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000000..0ec303439225 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000000..0ec303439225 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000000..e9f5fea27c70 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000000..84ac32ae7d98 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000000..8953cba09064 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000000..0467bf12aa4d Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000..f2e259c7c939 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/Main.storyboard b/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Info.plist b/packages/cross_file/cross_file_darwin/example/ios/Runner/Info.plist new file mode 100644 index 000000000000..f8648d3c710c --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Cross File Darwin + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + cross_file_darwin_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/Runner-Bridging-Header.h b/packages/cross_file/cross_file_darwin/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000000..ba04211afd0a --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// 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 "GeneratedPluginRegistrant.h" diff --git a/packages/cross_file/cross_file_darwin/example/ios/Runner/SceneDelegate.swift b/packages/cross_file/cross_file_darwin/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 000000000000..8c7b10c639d1 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,10 @@ +// 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 Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/packages/cross_file/cross_file_darwin/example/ios/RunnerTests/RunnerTests.swift b/packages/cross_file/cross_file_darwin/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000000..1e018c094ec5 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,22 @@ +// 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 Testing +import UIKit + +@testable import cross_file_darwin + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +struct RunnerTests { + @Test func testGetPlatform() { + #expect(true == true) + } +} diff --git a/packages/cross_file/cross_file_darwin/example/lib/main.dart b/packages/cross_file/cross_file_darwin/example/lib/main.dart new file mode 100644 index 000000000000..43b332515d93 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/lib/main.dart @@ -0,0 +1,134 @@ +// 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:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:mime/mime.dart' as mime; + +void main() { + runApp(const MaterialApp(home: FileOpenScreen())); +} + +/// Example screen to open a file selector and display it. +class FileOpenScreen extends StatelessWidget { + /// Constructs a [FileOpenScreen]. + const FileOpenScreen({super.key}); + + Future _openFile(BuildContext context) async { + final XFile? file = await openFile(); + + if (file case final XFile file) { + final String filename = await file.name() ?? file.uri; + + switch (mime.lookupMimeType(filename)) { + case final String mimeType when mimeType.startsWith('text'): + final String fileContents = await file.readAsString(); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(filename: filename, fileContents: fileContents), + ); + } + case _: + debugPrint('File Uri: ${file.uri}'); + debugPrint('Filename: $filename'); + debugPrint('Can Read File: ${await file.canRead()}'); + debugPrint('File Length: ${await file.length()}'); + debugPrint('File Last Modified: ${await file.lastModified()}'); + return; + } + } else { + debugPrint('No file selected.'); + } + } + + Future _openDirectory() async { + final XDirectory? directory = await getDirectoryPath(); + + if (directory != null) { + debugPrint('Directory Uri: ${directory.uri}'); + debugPrint('Directory exists: ${await directory.exists()}'); + + debugPrint('List of Entities:'); + await for (final XFileEntity entity in directory.list()) { + switch (entity) { + case final XFile file: + final String filename = await file.name() ?? file.uri; + debugPrint('\tFile: $filename'); + debugPrint('\t\tFile Length: ${await file.length()}'); + case final XDirectory directory: + debugPrint('\tDirectory: ${directory.uri}'); + } + } + } else { + debugPrint('No directory selected.'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Open a File'), + backgroundColor: Colors.blue, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open File'), + onPressed: () => _openFile(context), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open Directory'), + onPressed: () => _openDirectory(), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Default Constructor. + const TextDisplay({ + super.key, + required this.filename, + required this.fileContents, + }); + + /// The name of the file. + final String filename; + + /// The contents of the file. + final String fileContents; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(filename), + content: Scrollbar( + child: SingleChildScrollView(child: Text(fileContents)), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000000..4b81f9b2d200 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Release.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000000..5caa9d1579e4 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/macos/Podfile b/packages/cross_file/cross_file_darwin/example/macos/Podfile new file mode 100644 index 000000000000..ff5ddb3b8bdc --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.pbxproj b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..ce4c04d9e025 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,804 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 1E3F930A949E6BF1A392A157 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 542EA9A2F3AFEE0208F79BB1 /* Pods_RunnerTests.framework */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 8FB3B0B92F3D820100A1CEDF /* CrossFileDarwinApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB3B0B62F3D820100A1CEDF /* CrossFileDarwinApiTests.swift */; }; + 9365FDE22965FC1D9207E2E0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63A129979C19E770A95C46BB /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1CB2823D6DE86B76A18195DA /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* cross_file_darwin_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cross_file_darwin_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 542EA9A2F3AFEE0208F79BB1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 63A129979C19E770A95C46BB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 68E94AA7D5658C9B4705BC78 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6E84AA1E5316F40ABCA87AF9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7255794ECF94B4FB91F39D79 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8FB3B0B62F3D820100A1CEDF /* CrossFileDarwinApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CrossFileDarwinApiTests.swift; path = ../../darwin/Tests/CrossFileDarwinApiTests.swift; sourceTree = SOURCE_ROOT; }; + 95E600C17773E34356453E89 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AF56CD661CE6FC3643B381E8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E3F930A949E6BF1A392A157 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9365FDE22965FC1D9207E2E0 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 8FB3B0B62F3D820100A1CEDF /* CrossFileDarwinApiTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 91814838E1B0599FEE23D945 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* cross_file_darwin_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 91814838E1B0599FEE23D945 /* Pods */ = { + isa = PBXGroup; + children = ( + 95E600C17773E34356453E89 /* Pods-Runner.debug.xcconfig */, + 7255794ECF94B4FB91F39D79 /* Pods-Runner.release.xcconfig */, + 6E84AA1E5316F40ABCA87AF9 /* Pods-Runner.profile.xcconfig */, + 68E94AA7D5658C9B4705BC78 /* Pods-RunnerTests.debug.xcconfig */, + AF56CD661CE6FC3643B381E8 /* Pods-RunnerTests.release.xcconfig */, + 1CB2823D6DE86B76A18195DA /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 63A129979C19E770A95C46BB /* Pods_Runner.framework */, + 542EA9A2F3AFEE0208F79BB1 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 249AA0E568CF9FDCD7843723 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + D807BD7D820175A61286FA90 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 610737436CA1CAF7E249AF7E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* cross_file_darwin_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 249AA0E568CF9FDCD7843723 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 610737436CA1CAF7E249AF7E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D807BD7D820175A61286FA90 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8FB3B0B92F3D820100A1CEDF /* CrossFileDarwinApiTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 68E94AA7D5658C9B4705BC78 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_darwin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_darwin_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AF56CD661CE6FC3643B381E8 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_darwin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_darwin_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1CB2823D6DE86B76A18195DA /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cross_file_darwin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cross_file_darwin_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..af474038da98 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/AppDelegate.swift b/packages/cross_file/cross_file_darwin/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000000..88aaa2ff58ac --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// 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 Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..a2ec33f19f11 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000000..82b6f9d9a33e Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000000..13b35eba55c6 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000000..0a3f5fa40fb3 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000000..bdb57226d5f2 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000000..f083318e09ca Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000000..326c0e72c9d8 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000000..2f1632cfddf3 Binary files /dev/null and b/packages/cross_file/cross_file_darwin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/cross_file/cross_file_darwin/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000000..80e867a4e06b --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000000..41042182cc52 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = cross_file_darwin_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.crossFileDarwinExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 dev.flutter.packages. All rights reserved. diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Debug.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000000..36b0fd9464f4 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Release.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000000..dff4f49561c8 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Warnings.xcconfig b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000000..42bcbf4780b1 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/DebugProfile.entitlements b/packages/cross_file/cross_file_darwin/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000000..68acb49b6928 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.files.user-selected.read-write + + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Info.plist b/packages/cross_file/cross_file_darwin/example/macos/Runner/Info.plist new file mode 100644 index 000000000000..4789daa6a443 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/MainFlutterWindow.swift b/packages/cross_file/cross_file_darwin/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000000..df2443c8ebba --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// 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 Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/cross_file/cross_file_darwin/example/macos/Runner/Release.entitlements b/packages/cross_file/cross_file_darwin/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000000..012d091fca66 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + + diff --git a/packages/cross_file/cross_file_darwin/example/pubspec.yaml b/packages/cross_file/cross_file_darwin/example/pubspec.yaml new file mode 100644 index 000000000000..dd493f160dc7 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/example/pubspec.yaml @@ -0,0 +1,38 @@ +name: cross_file_darwin_example +description: "Demonstrates how to use the cross_file_darwin plugin." +publish_to: 'none' + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +dependencies: + cupertino_icons: ^1.0.8 + file_selector: + git: + url: https://github.com/bparrishMines/packages + ref: new_file_selector + path: packages/file_selector/file_selector + flutter: + sdk: flutter + mime: ^2.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/hello.txt + +dependency_overrides: + cross_file_darwin: + # When depending on this package from a real application you should use: + # cross_file_darwin: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ \ No newline at end of file diff --git a/packages/cross_file/cross_file_darwin/lib/cross_file_darwin.dart b/packages/cross_file/cross_file_darwin/lib/cross_file_darwin.dart new file mode 100644 index 000000000000..ddc7700c87b6 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/cross_file_darwin.dart @@ -0,0 +1,22 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart' + show + CrossFilePlatform, + ListParams, + PlatformScopedStorageXDirectoryCreationParams, + PlatformScopedStorageXFileCreationParams, + PlatformXFileEntity; + +export 'src/cross_file_darwin.dart'; +export 'src/darwin_scoped_storage_cross_directory.dart' + show + DarwinScopedStorageXDirectoryCreationParams, + DarwinScopedStorageXDirectoryExtension; +export 'src/darwin_scoped_storage_cross_file.dart' + show + DarwinScopedStorageXFileCreationParams, + DarwinScopedStorageXFileExtension; +export 'src/security_scoped_resource.dart'; diff --git a/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin.dart b/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin.dart new file mode 100644 index 000000000000..f00f2dc02852 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin.dart @@ -0,0 +1,31 @@ +// 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_io/cross_file_io.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; + +import 'darwin_scoped_storage_cross_directory.dart'; +import 'darwin_scoped_storage_cross_file.dart'; + +/// Implementation of [CrossFilePlatform] for iOS and macOS. +base class CrossFileDarwin extends CrossFileIO { + /// Registers this class as the default instance of [CrossFilePlatform]. + static void registerWith() { + CrossFilePlatform.instance = CrossFileDarwin(); + } + + @override + DarwinScopedStorageXFile createPlatformScopedStorageXFile( + PlatformScopedStorageXFileCreationParams params, + ) { + return DarwinScopedStorageXFile(params); + } + + @override + DarwinScopedStorageXDirectory createPlatformScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams params, + ) { + return DarwinScopedStorageXDirectory(params); + } +} diff --git a/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin_apis.g.dart b/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin_apis.g.dart new file mode 100644 index 000000000000..591a0daa863c --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/src/cross_file_darwin_apis.g.dart @@ -0,0 +1,145 @@ +// 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. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Api for getting access to file information. +class CrossFileDarwinApi { + /// Constructor for [CrossFileDarwinApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + CrossFileDarwinApi({ + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty + ? '.$messageChannelSuffix' + : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + /// Attempt to create a bookmarked URL that serves as a persistent reference + /// to a file. + Future tryCreateBookmarkedUrl(String url) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.tryCreateBookmarkedUrl$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [url], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + /// In an app that has adopted App Sandbox, makes the resource pointed to by a + /// security-scoped URL available to the app. + Future startAccessingSecurityScopedResource(String url) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.startAccessingSecurityScopedResource$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [url], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// In an app that adopts App Sandbox, revokes access to the resource pointed + /// to by a security-scoped URL. + Future stopAccessingSecurityScopedResource(String url) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.cross_file_darwin.CrossFileDarwinApi.stopAccessingSecurityScopedResource$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [url], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_directory.dart b/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_directory.dart new file mode 100644 index 000000000000..fe459c923cd5 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_directory.dart @@ -0,0 +1,103 @@ +// 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 'dart:io'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'cross_file_darwin_apis.g.dart'; +import 'darwin_scoped_storage_cross_file.dart'; +import 'security_scoped_resource.dart'; + +/// Implementation of [PlatformXDirectoryCreationParams] for iOS and +/// macOS. +@immutable +base class DarwinScopedStorageXDirectoryCreationParams + extends PlatformScopedStorageXDirectoryCreationParams { + /// Constructs a [DarwinScopedStorageXDirectoryCreationParams]. + DarwinScopedStorageXDirectoryCreationParams({ + required super.uri, + @visibleForTesting CrossFileDarwinApi? api, + }) : api = api ?? CrossFileDarwinApi(); + + /// Constructs an [DarwinScopedStorageXDirectoryCreationParams] from a + /// [PlatformScopedStorageXDirectoryCreationParams]. + factory DarwinScopedStorageXDirectoryCreationParams.fromCreationParams( + PlatformScopedStorageXDirectoryCreationParams params, { + @visibleForTesting CrossFileDarwinApi? api, + }) { + return DarwinScopedStorageXDirectoryCreationParams( + uri: params.uri, + api: api, + ); + } + + /// The API used to call to native code to interact with files. + @visibleForTesting + final CrossFileDarwinApi api; +} + +/// Implementation of [PlatformScopedStorageXDirectory] for iOS and macOS. +base class DarwinScopedStorageXDirectory extends PlatformScopedStorageXDirectory + with DarwinScopedStorageXDirectoryExtension { + /// Constructs a [DarwinScopedStorageXDirectory]. + DarwinScopedStorageXDirectory(super.params) : super.implementation(); + + late final _directory = Directory.fromUri(Uri.parse(params.uri)); + + @override + late final DarwinScopedStorageXDirectoryCreationParams params = + super.params is DarwinScopedStorageXDirectoryCreationParams + ? super.params as DarwinScopedStorageXDirectoryCreationParams + : DarwinScopedStorageXDirectoryCreationParams.fromCreationParams( + super.params, + ); + + @override + DarwinScopedStorageXDirectoryExtension? get extension => this; + + @override + Future exists() async => _directory.existsSync(); + + @override + Stream list(ListParams params) async* { + await for (final FileSystemEntity entity in _directory.list()) { + switch (entity) { + case final Directory directory: + yield DarwinScopedStorageXDirectory( + DarwinScopedStorageXDirectoryCreationParams( + uri: directory.uri.toString(), + ), + ); + case final File file: + yield DarwinScopedStorageXFile( + DarwinScopedStorageXFileCreationParams(uri: file.uri.toString()), + ); + } + } + } + + @override + Future startAccessingSecurityScopedResource() { + return params.api.startAccessingSecurityScopedResource(params.uri); + } + + @override + Future stopAccessingSecurityScopedResource() { + return params.api.stopAccessingSecurityScopedResource(params.uri); + } + + @override + Future toBookmarkedUri() async { + return params.api.tryCreateBookmarkedUrl(params.uri); + } +} + +/// Provides platform specific features for [DarwinScopedStorageXDirectory]. +mixin DarwinScopedStorageXDirectoryExtension + implements + PlatformScopedStorageXDirectoryExtension, + SecurityScopedResource {} diff --git a/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_file.dart b/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_file.dart new file mode 100644 index 000000000000..0c726ceee330 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/src/darwin_scoped_storage_cross_file.dart @@ -0,0 +1,114 @@ +// 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 'dart:convert'; +import 'dart:io'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as path; + +import 'cross_file_darwin_apis.g.dart'; +import 'security_scoped_resource.dart'; + +/// Implementation of [DarwinScopedStorageXFileCreationParams] for iOS and +/// macOS. +@immutable +base class DarwinScopedStorageXFileCreationParams + extends PlatformScopedStorageXFileCreationParams { + /// Constructs a [DarwinScopedStorageXFileCreationParams]. + DarwinScopedStorageXFileCreationParams({ + required super.uri, + @visibleForTesting CrossFileDarwinApi? api, + }) : api = api ?? CrossFileDarwinApi(); + + /// Constructs an [DarwinScopedStorageXFileCreationParams] from a + /// [PlatformScopedStorageXFileCreationParams]. + factory DarwinScopedStorageXFileCreationParams.fromCreationParams( + PlatformXFileCreationParams params, { + @visibleForTesting CrossFileDarwinApi? api, + }) { + return DarwinScopedStorageXFileCreationParams(uri: params.uri, api: api); + } + + /// The API used to call to native code to interact with files. + @visibleForTesting + final CrossFileDarwinApi api; +} + +/// Implementation of [PlatformScopedStorageXFile] for iOS and macOS. +base class DarwinScopedStorageXFile extends PlatformScopedStorageXFile + with DarwinScopedStorageXFileExtension { + /// Constructs a [DarwinScopedStorageXFile]. + DarwinScopedStorageXFile(super.params) : super.implementation(); + + late final _file = File.fromUri(Uri.parse(params.uri)); + + @override + late final DarwinScopedStorageXFileCreationParams params = + super.params is DarwinScopedStorageXFileCreationParams + ? super.params as DarwinScopedStorageXFileCreationParams + : DarwinScopedStorageXFileCreationParams.fromCreationParams(super.params); + + @override + DarwinScopedStorageXFileExtension? get extension => this; + + @override + Future lastModified() async { + try { + return _file.lastModifiedSync(); + } on FileSystemException { + return null; + } + } + + @override + Future length() async { + try { + return _file.length(); + } on FileSystemException { + return null; + } + } + + @override + Stream openRead([int? start, int? end]) => + _file.openRead(start, end).cast(); + + @override + Future readAsBytes() => _file.readAsBytes(); + + @override + Future readAsString({Encoding encoding = utf8}) => + _file.readAsString(encoding: encoding); + + @override + Future canRead() => exists(); + + @override + Future exists() async => _file.existsSync(); + + @override + Future name() async => path.basename(_file.path); + + @override + Future startAccessingSecurityScopedResource() { + return params.api.startAccessingSecurityScopedResource(params.uri); + } + + @override + Future stopAccessingSecurityScopedResource() { + return params.api.stopAccessingSecurityScopedResource(params.uri); + } + + @override + Future toBookmarkedUri() async { + return params.api.tryCreateBookmarkedUrl(params.uri); + } +} + +/// Provides platform specific features for [DarwinScopedStorageXFile]. +mixin DarwinScopedStorageXFileExtension + implements PlatformScopedStorageXFileExtension, SecurityScopedResource {} diff --git a/packages/cross_file/cross_file_darwin/lib/src/security_scoped_resource.dart b/packages/cross_file/cross_file_darwin/lib/src/security_scoped_resource.dart new file mode 100644 index 000000000000..c9bb5543665e --- /dev/null +++ b/packages/cross_file/cross_file_darwin/lib/src/security_scoped_resource.dart @@ -0,0 +1,21 @@ +// 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. + +/// Common methods for interacting with a security-scoped URL. +mixin SecurityScopedResource { + /// In an app that has adopted App Sandbox, makes the resource pointed to by a + /// security-scoped URL available to the app. + Future startAccessingSecurityScopedResource(); + + /// In an app that adopts App Sandbox, revokes access to the resource pointed + /// to by a security-scoped URL. + Future stopAccessingSecurityScopedResource(); + + /// Attempt to create a bookmarked URI that serves as a persistent reference + /// to the resource. + /// + /// Throws exception if the file could not be bookmarked or null if the + /// bookmark is stale. + Future toBookmarkedUri(); +} diff --git a/packages/cross_file/cross_file_darwin/pigeons/copyright.txt b/packages/cross_file/cross_file_darwin/pigeons/copyright.txt new file mode 100644 index 000000000000..07e5f8598a80 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. diff --git a/packages/cross_file/cross_file_darwin/pigeons/cross_file_darwin_apis.dart b/packages/cross_file/cross_file_darwin/pigeons/cross_file_darwin_apis.dart new file mode 100644 index 000000000000..1184477e8be3 --- /dev/null +++ b/packages/cross_file/cross_file_darwin/pigeons/cross_file_darwin_apis.dart @@ -0,0 +1,31 @@ +// 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. + +// ignore_for_file: avoid_unused_constructor_parameters + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/cross_file_darwin_apis.g.dart', + swiftOut: + 'darwin/cross_file_darwin/Sources/cross_file_darwin/CrossFileDarwinApis.g.swift', + copyrightHeader: 'pigeons/copyright.txt', + ), +) +/// Api for getting access to file information. +@HostApi() +abstract class CrossFileDarwinApi { + /// Attempt to create a bookmarked URL that serves as a persistent reference + /// to a file. + String? tryCreateBookmarkedUrl(String url); + + /// In an app that has adopted App Sandbox, makes the resource pointed to by a + /// security-scoped URL available to the app. + bool startAccessingSecurityScopedResource(String url); + + /// In an app that adopts App Sandbox, revokes access to the resource pointed + /// to by a security-scoped URL. + void stopAccessingSecurityScopedResource(String url); +} diff --git a/packages/cross_file/cross_file_darwin/pubspec.yaml b/packages/cross_file/cross_file_darwin/pubspec.yaml new file mode 100644 index 000000000000..172ca50ef76a --- /dev/null +++ b/packages/cross_file/cross_file_darwin/pubspec.yaml @@ -0,0 +1,48 @@ +name: cross_file_darwin +description: Implementation of cross_file_platform_interface for iOS and macOS. +repository: https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_darwin +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 +version: 0.0.1 + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +flutter: + plugin: + implements: cross_file + platforms: + ios: + dartPluginClass: CrossFileDarwin + pluginClass: CrossFileDarwinPlugin + sharedDarwinSource: true + macos: + dartPluginClass: CrossFileDarwin + pluginClass: CrossFileDarwinPlugin + sharedDarwinSource: true + +dependencies: + cross_file_io: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_io + cross_file_platform_interface: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_platform_interface + flutter: + sdk: flutter + path: ^1.9.1 + +dev_dependencies: + build_runner: ^2.10.4 + flutter_test: + sdk: flutter + mockito: ^5.4.4 + pigeon: ^26.1.4 + +topics: + - file + - cross-file diff --git a/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.dart b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.dart new file mode 100644 index 000000000000..95bc7216149d --- /dev/null +++ b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.dart @@ -0,0 +1,58 @@ +// 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_darwin/src/cross_file_darwin_apis.g.dart'; +import 'package:cross_file_darwin/src/darwin_scoped_storage_cross_directory.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'darwin_scoped_storage_cross_directory_test.mocks.dart'; + +@GenerateMocks([CrossFileDarwinApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('startAccessingSecurityScopedResource', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri/'; + const result = true; + when( + mockApi.startAccessingSecurityScopedResource(uri), + ).thenAnswer((_) async => result); + + final file = DarwinScopedStorageXDirectory( + DarwinScopedStorageXDirectoryCreationParams(uri: uri, api: mockApi), + ); + + expect(await file.startAccessingSecurityScopedResource(), result); + }); + + test('stopAccessingSecurityScopedResource', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri/'; + + final file = DarwinScopedStorageXDirectory( + DarwinScopedStorageXDirectoryCreationParams(uri: uri, api: mockApi), + ); + + await file.stopAccessingSecurityScopedResource(); + verify(mockApi.stopAccessingSecurityScopedResource(uri)); + }); + + test('tryCreateBookmarkedUrl', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri/'; + const bookmarkedUri = 'newUri/'; + when( + mockApi.tryCreateBookmarkedUrl(uri), + ).thenAnswer((_) async => bookmarkedUri); + + final file = DarwinScopedStorageXDirectory( + DarwinScopedStorageXDirectoryCreationParams(uri: uri, api: mockApi), + ); + + expect(await file.toBookmarkedUri(), bookmarkedUri); + }); +} diff --git a/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.mocks.dart b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.mocks.dart new file mode 100644 index 000000000000..95079740d0fd --- /dev/null +++ b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.mocks.dart @@ -0,0 +1,71 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cross_file_darwin/test/darwin_scoped_storage_cross_directory_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:cross_file_darwin/src/cross_file_darwin_apis.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +/// A class which mocks [CrossFileDarwinApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrossFileDarwinApi extends _i1.Mock + implements _i2.CrossFileDarwinApi { + MockCrossFileDarwinApi() { + _i1.throwOnMissingStub(this); + } + + @override + String get pigeonVar_messageChannelSuffix => + (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) + as String); + + @override + _i4.Future tryCreateBookmarkedUrl(String? url) => + (super.noSuchMethod( + Invocation.method(#tryCreateBookmarkedUrl, [url]), + returnValue: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future startAccessingSecurityScopedResource(String? url) => + (super.noSuchMethod( + Invocation.method(#startAccessingSecurityScopedResource, [url]), + returnValue: _i4.Future.value(false), + ) + as _i4.Future); + + @override + _i4.Future stopAccessingSecurityScopedResource(String? url) => + (super.noSuchMethod( + Invocation.method(#stopAccessingSecurityScopedResource, [url]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} diff --git a/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.dart b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.dart new file mode 100644 index 000000000000..ac9d86783e8e --- /dev/null +++ b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.dart @@ -0,0 +1,58 @@ +// 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_darwin/src/cross_file_darwin_apis.g.dart'; +import 'package:cross_file_darwin/src/darwin_scoped_storage_cross_file.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'darwin_scoped_storage_cross_file_test.mocks.dart'; + +@GenerateMocks([CrossFileDarwinApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('startAccessingSecurityScopedResource', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri'; + const result = true; + when( + mockApi.startAccessingSecurityScopedResource(uri), + ).thenAnswer((_) async => result); + + final file = DarwinScopedStorageXFile( + DarwinScopedStorageXFileCreationParams(uri: uri, api: mockApi), + ); + + expect(await file.startAccessingSecurityScopedResource(), result); + }); + + test('stopAccessingSecurityScopedResource', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri'; + + final file = DarwinScopedStorageXFile( + DarwinScopedStorageXFileCreationParams(uri: uri, api: mockApi), + ); + + await file.stopAccessingSecurityScopedResource(); + verify(mockApi.stopAccessingSecurityScopedResource(uri)); + }); + + test('tryCreateBookmarkedUrl', () async { + final mockApi = MockCrossFileDarwinApi(); + const uri = 'uri'; + const bookmarkedUri = 'newUri'; + when( + mockApi.tryCreateBookmarkedUrl(uri), + ).thenAnswer((_) async => bookmarkedUri); + + final file = DarwinScopedStorageXFile( + DarwinScopedStorageXFileCreationParams(uri: uri, api: mockApi), + ); + + expect(await file.toBookmarkedUri(), bookmarkedUri); + }); +} diff --git a/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.mocks.dart b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.mocks.dart new file mode 100644 index 000000000000..5806deaa1dea --- /dev/null +++ b/packages/cross_file/cross_file_darwin/test/darwin_scoped_storage_cross_file_test.mocks.dart @@ -0,0 +1,71 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cross_file_darwin/test/darwin_scoped_storage_cross_file_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:cross_file_darwin/src/cross_file_darwin_apis.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +/// A class which mocks [CrossFileDarwinApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCrossFileDarwinApi extends _i1.Mock + implements _i2.CrossFileDarwinApi { + MockCrossFileDarwinApi() { + _i1.throwOnMissingStub(this); + } + + @override + String get pigeonVar_messageChannelSuffix => + (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) + as String); + + @override + _i4.Future tryCreateBookmarkedUrl(String? url) => + (super.noSuchMethod( + Invocation.method(#tryCreateBookmarkedUrl, [url]), + returnValue: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future startAccessingSecurityScopedResource(String? url) => + (super.noSuchMethod( + Invocation.method(#startAccessingSecurityScopedResource, [url]), + returnValue: _i4.Future.value(false), + ) + as _i4.Future); + + @override + _i4.Future stopAccessingSecurityScopedResource(String? url) => + (super.noSuchMethod( + Invocation.method(#stopAccessingSecurityScopedResource, [url]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} diff --git a/packages/cross_file/cross_file_io/AUTHORS b/packages/cross_file/cross_file_io/AUTHORS new file mode 100644 index 000000000000..557dff97933b --- /dev/null +++ b/packages/cross_file/cross_file_io/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 Inc. diff --git a/packages/cross_file/cross_file_io/CHANGELOG.md b/packages/cross_file/cross_file_io/CHANGELOG.md new file mode 100644 index 000000000000..d0bd041d0ff6 --- /dev/null +++ b/packages/cross_file/cross_file_io/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release. diff --git a/packages/cross_file/cross_file_io/LICENSE b/packages/cross_file/cross_file_io/LICENSE new file mode 100644 index 000000000000..29b709dac6c7 --- /dev/null +++ b/packages/cross_file/cross_file_io/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_io/README.md b/packages/cross_file/cross_file_io/README.md new file mode 100644 index 000000000000..2dd438a8e28d --- /dev/null +++ b/packages/cross_file/cross_file_io/README.md @@ -0,0 +1,15 @@ +# cross\_file\_io + +The `dart:io` implementation of [`cross_file`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `cross_file` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +[1]: https://pub.dev/packages/cross_file +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/cross_file/cross_file_io/lib/cross_file_io.dart b/packages/cross_file/cross_file_io/lib/cross_file_io.dart new file mode 100644 index 000000000000..7ce95334bd36 --- /dev/null +++ b/packages/cross_file/cross_file_io/lib/cross_file_io.dart @@ -0,0 +1,15 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart' + show + CrossFilePlatform, + ListParams, + PlatformXDirectoryCreationParams, + PlatformXFileCreationParams, + PlatformXFileEntity; + +export 'src/cross_file_io.dart'; +export 'src/io_cross_directory.dart'; +export 'src/io_cross_file.dart'; diff --git a/packages/cross_file/cross_file_io/lib/src/cross_file_io.dart b/packages/cross_file/cross_file_io/lib/src/cross_file_io.dart new file mode 100644 index 000000000000..4382dbce0a9e --- /dev/null +++ b/packages/cross_file/cross_file_io/lib/src/cross_file_io.dart @@ -0,0 +1,28 @@ +// 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 'io_cross_directory.dart'; +import 'io_cross_file.dart'; + +/// Implementation of [CrossFilePlatform] for dart:io. +base class CrossFileIO extends CrossFilePlatform { + /// Registers this class as the default instance of [CrossFilePlatform]. + static void registerWith() { + CrossFilePlatform.instance = CrossFileIO(); + } + + @override + IOXFile createPlatformXFile(PlatformXFileCreationParams params) { + return IOXFile(params); + } + + @override + IOXDirectory createPlatformXDirectory( + PlatformXDirectoryCreationParams params, + ) { + return IOXDirectory(params); + } +} diff --git a/packages/cross_file/cross_file_io/lib/src/io_cross_directory.dart b/packages/cross_file/cross_file_io/lib/src/io_cross_directory.dart new file mode 100644 index 000000000000..380ce720751c --- /dev/null +++ b/packages/cross_file/cross_file_io/lib/src/io_cross_directory.dart @@ -0,0 +1,73 @@ +// 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:io'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'io_cross_file.dart'; + +/// Implementation of [PlatformXDirectoryCreationParams] for dart:io. +@immutable +base class IOXDirectoryCreationParams extends PlatformXDirectoryCreationParams { + /// Constructs an [IOXDirectoryCreationParams]. + IOXDirectoryCreationParams({required String uri}) + : this.fromDirectory(Directory.fromUri(Uri.parse(uri))); + + /// Constructs an [IOXDirectoryCreationParams] from a [Directory]. + IOXDirectoryCreationParams.fromDirectory(this.directory) + : super(uri: directory.uri.toString()); + + /// Constructs an [IOXDirectoryCreationParams] from a [PlatformXDirectoryCreationParams]. + factory IOXDirectoryCreationParams.fromCreationParams( + PlatformXDirectoryCreationParams params, + ) { + return IOXDirectoryCreationParams(uri: params.uri); + } + + /// The underlying [Directory] for [IOXDirectory]. + final Directory directory; +} + +/// Implementation of [PlatformXDirectory] for dart:io. +base class IOXDirectory extends PlatformXDirectory with IOXDirectoryExtension { + /// Constructs an [IOXDirectory]. + IOXDirectory(super.params) : super.implementation(); + + @override + late final IOXDirectoryCreationParams params = + super.params is IOXDirectoryCreationParams + ? super.params as IOXDirectoryCreationParams + : IOXDirectoryCreationParams.fromCreationParams(super.params); + + @override + Directory get directory => params.directory; + + @override + IOXDirectoryExtension? get extension => this; + + @override + Future exists() async => directory.existsSync(); + + @override + Stream list(ListParams params) async* { + await for (final FileSystemEntity entity in directory.list()) { + switch (entity) { + case final Directory directory: + yield IOXDirectory( + IOXDirectoryCreationParams.fromDirectory(directory), + ); + case final File file: + yield IOXFile(IOXFileCreationParams.fromFile(file)); + } + } + } +} + +/// Provides platform specific features for [IOXDirectory]. +mixin IOXDirectoryExtension implements PlatformXDirectoryExtension { + /// The underlying directory. + Directory get directory; +} diff --git a/packages/cross_file/cross_file_io/lib/src/io_cross_file.dart b/packages/cross_file/cross_file_io/lib/src/io_cross_file.dart new file mode 100644 index 000000000000..8857bce6b68a --- /dev/null +++ b/packages/cross_file/cross_file_io/lib/src/io_cross_file.dart @@ -0,0 +1,93 @@ +// 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:io'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as path; + +/// Implementation of [PlatformXFileCreationParams] for dart:io. +@immutable +base class IOXFileCreationParams extends PlatformXFileCreationParams { + /// Constructs an [IOXFileCreationParams]. + IOXFileCreationParams({required String uri}) + : this.fromFile(File.fromUri(Uri.parse(uri))); + + /// Constructs an [IOXFileCreationParams] from a [File]. + IOXFileCreationParams.fromFile(this.file) : super(uri: file.uri.toString()); + + /// Constructs an [IOXFileCreationParams] from a [PlatformXFileCreationParams]. + factory IOXFileCreationParams.fromCreationParams( + PlatformXFileCreationParams params, + ) { + return IOXFileCreationParams(uri: params.uri); + } + + /// The underlying [File] for [IOXFile]. + final File file; +} + +/// Implementation of [PlatformXFile] for dart:io. +base class IOXFile extends PlatformXFile with IOXFileExtension { + /// Constructs an [IOXFile]. + IOXFile(super.params) : super.implementation(); + + @override + late final IOXFileCreationParams params = + super.params is IOXFileCreationParams + ? super.params as IOXFileCreationParams + : IOXFileCreationParams.fromCreationParams(super.params); + + @override + File get file => params.file; + + @override + PlatformXFileExtension? get extension => this; + + @override + Future lastModified() async { + try { + return file.lastModifiedSync(); + } on FileSystemException { + return null; + } + } + + @override + Future length() async { + try { + return file.length(); + } on FileSystemException { + return null; + } + } + + @override + Stream openRead([int? start, int? end]) => + file.openRead(start, end).cast(); + + @override + Future readAsBytes() => file.readAsBytes(); + + @override + Future readAsString({Encoding encoding = utf8}) => + file.readAsString(encoding: encoding); + + @override + Future canRead() => exists(); + + @override + Future exists() async => file.existsSync(); + + @override + Future name() async => path.basename(file.path); +} + +/// Provides platform specific features for [IOXFile]. +mixin IOXFileExtension implements PlatformXFileExtension { + /// The underlying file. + File get file; +} diff --git a/packages/cross_file/cross_file_io/pubspec.yaml b/packages/cross_file/cross_file_io/pubspec.yaml new file mode 100644 index 000000000000..e800f29e4e18 --- /dev/null +++ b/packages/cross_file/cross_file_io/pubspec.yaml @@ -0,0 +1,36 @@ +name: cross_file_io +description: Implementation of cross_file_platform_interface for dart:io. +repository: https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_io +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 +version: 0.0.1 + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +flutter: + plugin: + implements: cross_file + platforms: + linux: + dartPluginClass: CrossFileIO + windows: + dartPluginClass: CrossFileIO + +dependencies: + cross_file_platform_interface: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_platform_interface + flutter: + sdk: flutter + path: ^1.9.1 + +dev_dependencies: + flutter_test: + sdk: flutter + +topics: + - files + - cross-file diff --git a/packages/cross_file/cross_file_io/test/io_cross_directory_test.dart b/packages/cross_file/cross_file_io/test/io_cross_directory_test.dart new file mode 100644 index 000000000000..94ae20734561 --- /dev/null +++ b/packages/cross_file/cross_file_io/test/io_cross_directory_test.dart @@ -0,0 +1,45 @@ +// 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:io'; + +import 'package:cross_file_io/cross_file_io.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; + +final Directory testDirectory = Directory( + path.join(Directory.current.path, 'test'), +); + +void main() { + group('IOXFile', () { + setUp(() { + CrossFilePlatform.instance = CrossFileIO(); + }); + + test('exists', () async { + final directory = PlatformXDirectory( + PlatformXDirectoryCreationParams(uri: testDirectory.path), + ); + + expect(await directory.exists(), testDirectory.existsSync()); + }); + + test('list', () async { + final directory = PlatformXDirectory( + PlatformXDirectoryCreationParams(uri: testDirectory.path), + ); + + expect( + (await directory.list(ListParams()).toList()).map( + (PlatformXFileEntity entity) => entity.params.uri, + ), + (await testDirectory.list().toList()).map( + (FileSystemEntity entity) => entity.uri.toString(), + ), + ); + }); + }); +} diff --git a/packages/cross_file/cross_file_io/test/io_cross_file_test.dart b/packages/cross_file/cross_file_io/test/io_cross_file_test.dart new file mode 100644 index 000000000000..33087c169933 --- /dev/null +++ b/packages/cross_file/cross_file_io/test/io_cross_file_test.dart @@ -0,0 +1,89 @@ +// 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:io'; + +import 'package:cross_file_io/cross_file_io.dart'; +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; + +final File testFile = File( + path.join(Directory.current.path, 'test', 'test_file.txt'), +); + +void main() { + group('IOXFile', () { + setUp(() { + CrossFilePlatform.instance = CrossFileIO(); + }); + + test('lastModified', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.lastModified(), testFile.lastModifiedSync()); + }); + + test('length', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.length(), await testFile.length()); + }); + + test('openRead', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect( + await file.openRead().toList(), + await testFile.openRead().toList(), + ); + }); + + test('readAsBytes', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.readAsBytes(), await testFile.readAsBytes()); + }); + + test('readAsString', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.readAsString(), await testFile.readAsString()); + }); + + test('canRead', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.canRead(), testFile.existsSync()); + }); + + test('exists', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.exists(), testFile.existsSync()); + }); + + test('name', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFile.path), + ); + + expect(await file.name(), 'test_file.txt'); + }); + }); +} diff --git a/packages/cross_file/cross_file_io/test/test_file.txt b/packages/cross_file/cross_file_io/test/test_file.txt new file mode 100644 index 000000000000..b45ef6fec895 --- /dev/null +++ b/packages/cross_file/cross_file_io/test/test_file.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file 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..557dff97933b --- /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 Inc. 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..d0bd041d0ff6 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* 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..a6a1a738d73a --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/cross_file_platform_interface.dart @@ -0,0 +1,10 @@ +// 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/platform_cross_directory.dart'; +export 'src/platform_cross_file.dart'; +export 'src/platform_cross_file_entity.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..c10a8ab4cedf --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/cross_file_platform.dart @@ -0,0 +1,111 @@ +// 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 'platform_cross_directory.dart'; +import 'platform_cross_file.dart'; +import 'platform_cross_file_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 [PlatformXFile]. + PlatformXFile createPlatformXFile(PlatformXFileCreationParams params); + + /// Creates a new [PlatformXDirectory]. + PlatformXDirectory createPlatformXDirectory( + PlatformXDirectoryCreationParams params, + ) { + return _DefaultXDirectory(params); + } + + /// Creates a new [PlatformScopedStorageXDirectory]. + PlatformScopedStorageXFile createPlatformScopedStorageXFile( + PlatformScopedStorageXFileCreationParams params, + ) { + return _DefaultScopedStorageXFile(params); + } + + /// Creates a new [PlatformScopedStorageXDirectory]. + PlatformScopedStorageXDirectory createPlatformScopedStorageXDirectory( + PlatformScopedStorageXDirectoryCreationParams params, + ) { + return _DefaultScopedStorageXDirectory(params); + } +} + +/// Implementation of [PlatformXDirectory} that represents a directory that does +/// not exist. +final class _DefaultXDirectory extends PlatformXDirectory { + _DefaultXDirectory(super.params) : super.implementation(); + + @override + Future exists() async => false; + + @override + Stream list(ListParams params) { + 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]) { + 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.'); + } +} + +/// 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 + Stream list(ListParams params) { + throw UnsupportedError('This instance does not represent any directory.'); + } +} 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..87804e76c967 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_directory.dart @@ -0,0 +1,108 @@ +// 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'; + +import 'cross_file_platform.dart'; +import 'platform_cross_file_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 PlatformXFileEntityCreationParams { + /// Constructs a [PlatformXDirectoryCreationParams]. + const PlatformXDirectoryCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform specific features for implementations of +/// [PlatformXDirectory]. +/// +/// 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 AndroidXFileExtension { +/// // ... +/// @override +/// PlatformXDirectoryExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidXFileExtension implements PlatformXDirectoryExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformXDirectoryExtension implements PlatformXFileEntityExtension {} + +/// A reference to a directory (or folder) on the file system. +abstract base class PlatformXDirectory extends PlatformXFileEntity { + /// Creates a new [PlatformXDirectory]. + factory PlatformXDirectory(PlatformXDirectoryCreationParams 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!.createPlatformXDirectory(params); + } + + /// Used by the platform implementation to create a new [PlatformXDirectory]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformXDirectory.implementation( + PlatformXDirectoryCreationParams super.params, + ); + + @override + PlatformXDirectoryCreationParams get params => + super.params as PlatformXDirectoryCreationParams; + + /// 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(ListParams params); +} + +/// Base class for parameters passed to [PlatformXDirectory.list]. +@immutable +base class ListParams {} 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..3b878be7fe77 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file.dart @@ -0,0 +1,141 @@ +// 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 'package:flutter/foundation.dart'; + +import 'cross_file_platform.dart'; +import 'platform_cross_file_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 PlatformXFileEntityCreationParams { + /// Constructs a [PlatformXFileCreationParams]. + const PlatformXFileCreationParams({required super.uri}); +} + +/// Base mixin used to provide platform specific features for implementations of +/// [PlatformXFile]. +/// +/// 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 PlatformXFileEntityExtension {} + +/// Interface for a reference to a local data resource. +abstract base class PlatformXFile extends PlatformXFileEntity { + /// Creates a new [PlatformXFile]. + factory PlatformXFile(PlatformXFileCreationParams 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!.createPlatformXFile(params); + } + + /// Used by the platform implementation to create a new [PlatformXFile]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformXFile.implementation(PlatformXFileCreationParams super.params); + + @override + PlatformXFileCreationParams get params => + super.params as PlatformXFileCreationParams; + + /// 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(); + + /// Whether the resource represented by this reference can be read. + Future canRead(); + + /// 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. + /// + /// The path is excluded from this value. + /// + /// Returns null if file doesn't exist or information is not available. + Future name(); +} diff --git a/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file_entity.dart b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file_entity.dart new file mode 100644 index 000000000000..2c15f5849939 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/platform_cross_file_entity.dart @@ -0,0 +1,35 @@ +// 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'; + +/// The common superclass for [PlatformXFileCreationParams] and +/// [PlatformXDirectoryCreationParams]. +@immutable +abstract base class PlatformXFileEntityCreationParams { + /// Constructs a [PlatformXFileCreationParams]. + const PlatformXFileEntityCreationParams({required this.uri}); + + /// A string used to reference the resource's location. + final String uri; +} + +/// The common superclass for [PlatformXFileExtension] and +/// [PlatformXDirectoryExtension]. +mixin PlatformXFileEntityExtension {} + +/// The common superclass for [PlatformXFile] and [PlatformXDirectory]. +abstract base class PlatformXFileEntity { + /// Constructs a [PlatformCrossFileEntity]. + PlatformXFileEntity(this.params); + + /// The parameters used to initialize the [PlatformXFileEntity]. + final PlatformXFileEntityCreationParams params; + + /// Extension for providing platform specific features. + PlatformXFileEntityExtension? 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/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..bdeec05e45fd --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_directory.dart @@ -0,0 +1,102 @@ +// 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]. +/// +/// 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 AndroidXFileExtension { +/// // ... +/// @override +/// PlatformScopedStorageXDirectoryExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidXFileExtension implements PlatformScopedStorageXDirectoryExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformScopedStorageXDirectoryExtension + implements PlatformXDirectoryExtension {} + +/// A reference to a directory (or folder) on the file system within a devices +/// 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. + PlatformScopedStorageXDirectory.implementation( + PlatformScopedStorageXDirectoryCreationParams super.params, + ) : super.implementation(); + + @override + PlatformScopedStorageXDirectoryCreationParams get params => + super.params as PlatformScopedStorageXDirectoryCreationParams; +} 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..46c83c3aae81 --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/lib/src/scoped_storage/platform_scoped_storage_cross_file.dart @@ -0,0 +1,100 @@ +// 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]. +/// +/// 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 AndroidXFileExtension { +/// // ... +/// @override +/// PlatformScopedStorageXFileExtension? get extension => this; +/// +/// Future platformMethod() { +/// // ... +/// } +/// } +/// +/// mixin AndroidXFileExtension implements PlatformScopedStorageXFileExtension { +/// Future platformMethod(); +/// } +/// ``` +mixin PlatformScopedStorageXFileExtension implements PlatformXFileExtension {} + +/// Interface for a reference to a local data resource within a devices +/// 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, + ) : super.implementation(); + + @override + PlatformScopedStorageXFileCreationParams get params => + super.params as PlatformScopedStorageXFileCreationParams; +} 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..b0564b9a4139 --- /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: 0.0.1 + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.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..0ece34bcafee --- /dev/null +++ b/packages/cross_file/cross_file_platform_interface/test/cross_file_platform_test.dart @@ -0,0 +1,115 @@ +// 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:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CrossFilePlatform', () { + test( + 'Default implementation of createPlatformXDirectory should return an implementation that returns false for exists()', + () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformXDirectory( + const PlatformXDirectoryCreationParams(uri: 'test'), + ) + .exists(), + false, + ); + }, + ); + + test( + 'Default implementation of createPlatformScopedStorageXFile should return an implementation that returns false for exists()', + () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformScopedStorageXFile( + const PlatformScopedStorageXFileCreationParams(uri: 'test'), + ) + .exists(), + false, + ); + }, + ); + + test( + 'Default implementation of createPlatformScopedStorageXDirectory should return an implementation that returns false for exists()', + () async { + final platform = TestCrossFilePlatform(); + + expect( + await platform + .createPlatformScopedStorageXDirectory( + const PlatformScopedStorageXDirectoryCreationParams( + uri: 'test', + ), + ) + .exists(), + false, + ); + }, + ); + }); +} + +final class TestCrossFilePlatform extends CrossFilePlatform { + @override + PlatformXFile createPlatformXFile(PlatformXFileCreationParams params) { + return TestXFile(params); + } +} + +final class TestXFile extends PlatformXFile { + TestXFile(super.params) : super.implementation(); + + @override + Future canRead() { + throw UnimplementedError(); + } + + @override + Future exists() { + throw UnimplementedError(); + } + + @override + Future lastModified() { + throw UnimplementedError(); + } + + @override + Future length() { + throw UnimplementedError(); + } + + @override + Stream openRead([int? start, int? end]) { + throw UnimplementedError(); + } + + @override + Future readAsBytes() { + throw UnimplementedError(); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + throw UnimplementedError(); + } + + @override + Future name() { + throw UnimplementedError(); + } +} diff --git a/packages/cross_file/cross_file_web/CHANGELOG.md b/packages/cross_file/cross_file_web/CHANGELOG.md new file mode 100644 index 000000000000..d0bd041d0ff6 --- /dev/null +++ b/packages/cross_file/cross_file_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release. diff --git a/packages/cross_file/cross_file_web/LICENSE b/packages/cross_file/cross_file_web/LICENSE new file mode 100644 index 000000000000..29b709dac6c7 --- /dev/null +++ b/packages/cross_file/cross_file_web/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_web/README.md b/packages/cross_file/cross_file_web/README.md new file mode 100644 index 000000000000..a5063b9a37aa --- /dev/null +++ b/packages/cross_file/cross_file_web/README.md @@ -0,0 +1,36 @@ +# cross\_file\_web + +The web implementation of [`cross_file`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `cross_file` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +## Limitations + +`XFile` on the web platform is backed by [Blob](https://api.dart.dev/be/180361/dart-html/Blob-class.html) +objects and their URLs. + +It seems that Safari hangs when reading Blobs larger than 4GB (your app will stop without returning +any data, or throwing an exception). + +This package will attempt to throw an `Exception` before a large file is accessed from Safari (if +its size is known beforehand), so that case can be handled programmatically. + +### Browser compatibility + +[![Data on Global support for Blob constructing](https://caniuse.bitsofco.de/image/blobbuilder.png)](https://caniuse.com/blobbuilder) + +[![Data on Global support for Blob URLs](https://caniuse.bitsofco.de/image/bloburls.png)](https://caniuse.com/bloburls) + +### Tests + +Tests for the web platform can be run with `flutter test -d chrome`. + +[1]: https://pub.dev/packages/cross_file +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/cross_file/cross_file_web/example/README.md b/packages/cross_file/cross_file_web/example/README.md new file mode 100644 index 000000000000..932e9f227cbe --- /dev/null +++ b/packages/cross_file/cross_file_web/example/README.md @@ -0,0 +1,19 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. + +## Testing + +This package uses `package:integration_test` to run its tests in a web browser. + +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. + +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) +for more info. diff --git a/packages/cross_file/cross_file_web/example/integration_test/plugin_integration_test.dart b/packages/cross_file/cross_file_web/example/integration_test/plugin_integration_test.dart new file mode 100644 index 000000000000..621977be68c7 --- /dev/null +++ b/packages/cross_file/cross_file_web/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,14 @@ +// 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_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // Since this test is lacking integration tests, this test ensures the example + // app can be launched on an emulator/device. + testWidgets('Launch Test', (WidgetTester tester) async {}); +} diff --git a/packages/cross_file/cross_file_web/example/lib/main.dart b/packages/cross_file/cross_file_web/example/lib/main.dart new file mode 100644 index 000000000000..4ffd44234094 --- /dev/null +++ b/packages/cross_file/cross_file_web/example/lib/main.dart @@ -0,0 +1,134 @@ +// 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:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:mime/mime.dart' as mime; + +void main() { + runApp(const MaterialApp(home: FileOpenScreen())); +} + +/// Example screen to open a file selector and display it. +class FileOpenScreen extends StatelessWidget { + /// Constructs a [FileOpenScreen]. + const FileOpenScreen({super.key}); + + Future _openFile(BuildContext context) async { + final XFile? file = await openFile(); + + if (file != null) { + final String filename = await file.name() ?? file.uri; + + switch (mime.lookupMimeType(filename)) { + case final String mimeType when mimeType.startsWith('text'): + final String fileContents = await file.readAsString(); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(filename: filename, fileContents: fileContents), + ); + } + case _: + debugPrint('File Uri: ${file.uri}'); + debugPrint('Filename: $filename'); + debugPrint('Can Read File: ${await file.canRead()}'); + debugPrint('File Length: ${await file.length()}'); + debugPrint('File Last Modified: ${await file.lastModified()}'); + return; + } + } else { + debugPrint('No file selected.'); + } + } + + Future _openDirectory() async { + final XDirectory? directory = await getDirectoryPath(); + + if (directory != null) { + debugPrint('Directory Uri: ${directory.uri}'); + debugPrint('Directory exists: ${await directory.exists()}'); + + debugPrint('List of Entities:'); + await for (final XFileEntity entity in directory.list()) { + switch (entity) { + case final XFile file: + final String filename = await file.name() ?? file.uri; + debugPrint('\tFile: $filename'); + debugPrint('\t\tFile Length: ${await file.length()}'); + case final XDirectory directory: + debugPrint('\tDirectory: ${directory.uri}'); + } + } + } else { + debugPrint('No directory selected.'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Open a File'), + backgroundColor: Colors.blue, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open File'), + onPressed: () => _openFile(context), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ), + child: const Text('Open Directory'), + onPressed: () => _openDirectory(), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Default Constructor. + const TextDisplay({ + super.key, + required this.filename, + required this.fileContents, + }); + + /// The name of the file. + final String filename; + + /// The contents of the file. + final String fileContents; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(filename), + content: Scrollbar( + child: SingleChildScrollView(child: Text(fileContents)), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/cross_file/cross_file_web/example/pubspec.yaml b/packages/cross_file/cross_file_web/example/pubspec.yaml new file mode 100644 index 000000000000..a88c55a7a699 --- /dev/null +++ b/packages/cross_file/cross_file_web/example/pubspec.yaml @@ -0,0 +1,32 @@ +name: cross_file_web_example +publish_to: none + +environment: + sdk: ^3.8.0 + flutter: ">=3.32.0" + +dependencies: + file_selector: + git: + url: https://github.com/bparrishMines/packages + ref: new_file_selector + path: packages/file_selector/file_selector + flutter: + sdk: flutter + mime: ^2.0.0 + web: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +dependency_overrides: + cross_file_web: + # When depending on this package from a real application you should use: + # cross_file_web: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ diff --git a/packages/cross_file/cross_file_web/example/web/favicon.png b/packages/cross_file/cross_file_web/example/web/favicon.png new file mode 100644 index 000000000000..8aaa46ac1ae2 Binary files /dev/null and b/packages/cross_file/cross_file_web/example/web/favicon.png differ diff --git a/packages/cross_file/cross_file_web/example/web/icons/Icon-192.png b/packages/cross_file/cross_file_web/example/web/icons/Icon-192.png new file mode 100644 index 000000000000..b749bfef0747 Binary files /dev/null and b/packages/cross_file/cross_file_web/example/web/icons/Icon-192.png differ diff --git a/packages/cross_file/cross_file_web/example/web/icons/Icon-512.png b/packages/cross_file/cross_file_web/example/web/icons/Icon-512.png new file mode 100644 index 000000000000..88cfd48dff11 Binary files /dev/null and b/packages/cross_file/cross_file_web/example/web/icons/Icon-512.png differ diff --git a/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-192.png b/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000000..eb9b4d76e525 Binary files /dev/null and b/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-512.png b/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000000..d69c56691fbd Binary files /dev/null and b/packages/cross_file/cross_file_web/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/cross_file/cross_file_web/example/web/index.html b/packages/cross_file/cross_file_web/example/web/index.html new file mode 100644 index 000000000000..d29d23f431b0 --- /dev/null +++ b/packages/cross_file/cross_file_web/example/web/index.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + cross_file_web_example + + + + + + + diff --git a/packages/cross_file/cross_file_web/example/web/manifest.json b/packages/cross_file/cross_file_web/example/web/manifest.json new file mode 100644 index 000000000000..74f71b3a0ed8 --- /dev/null +++ b/packages/cross_file/cross_file_web/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "cross_file_web_example", + "short_name": "cross_file_web_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Demonstrates how to use the cross_file_web plugin.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/cross_file/cross_file_web/lib/cross_file_web.dart b/packages/cross_file/cross_file_web/lib/cross_file_web.dart new file mode 100644 index 000000000000..d634bccb57fa --- /dev/null +++ b/packages/cross_file/cross_file_web/lib/cross_file_web.dart @@ -0,0 +1,9 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart' + show CrossFilePlatform, PlatformXFileCreationParams; + +export 'src/cross_file_web.dart'; +export 'src/web_cross_file.dart' show WebXFileCreationParams, WebXFileExtension; diff --git a/packages/cross_file/cross_file_web/lib/src/cross_file_web.dart b/packages/cross_file/cross_file_web/lib/src/cross_file_web.dart new file mode 100644 index 000000000000..3c391370c395 --- /dev/null +++ b/packages/cross_file/cross_file_web/lib/src/cross_file_web.dart @@ -0,0 +1,21 @@ +// 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_web_plugins/flutter_web_plugins.dart'; + +import 'web_cross_file.dart'; + +/// Implementation of [CrossFilePlatform] for web. +base class CrossFileWeb extends CrossFilePlatform { + /// Registers this class as the default instance of [CrossFilePlatform]. + static void registerWith(Registrar registrar) { + CrossFilePlatform.instance = CrossFileWeb(); + } + + @override + WebXFile createPlatformXFile(PlatformXFileCreationParams params) { + return WebXFile(params); + } +} diff --git a/packages/cross_file/cross_file_web/lib/src/web_cross_file.dart b/packages/cross_file/cross_file_web/lib/src/web_cross_file.dart new file mode 100644 index 000000000000..02974bb518e6 --- /dev/null +++ b/packages/cross_file/cross_file_web/lib/src/web_cross_file.dart @@ -0,0 +1,183 @@ +// 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 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:web/web.dart'; + +import 'web_helpers.dart'; + +/// Base implementation of [PlatformXFileCreationParams] for web. +@immutable +sealed class WebXFileCreationParams extends PlatformXFileCreationParams { + /// Constructs a [WebXFileCreationParams]. + const WebXFileCreationParams({required super.uri, this.testOverrides}); + + /// Constructs a [WebXFileCreationParams] with an object url. + factory WebXFileCreationParams.fromObjectUrl({ + required String objectUrl, + @visibleForTesting XFileTestOverrides? testOverrides, + }) => UrlWebXFileCreationParams( + objectUrl: objectUrl, + testOverrides: testOverrides, + ); + + /// Constructs a [WebXFileCreationParams] with a [Blob]. + /// + /// Creates an Object URL using the provided blob. See [URL.createObjectURL] + /// + /// `autoRevokeObjectUrl`: Whether the unique object url obtained created with + /// [blob] should be revoked when this instance is garbage collected. See + /// [URL.revokeObjectURL]. + factory WebXFileCreationParams.fromBlob( + Blob blob, { + bool autoRevokeObjectUrl = true, + @visibleForTesting XFileTestOverrides? testOverrides, + }) => BlobWebXFileCreationParams( + blob, + autoRevokeObjectUrl: autoRevokeObjectUrl, + testOverrides: testOverrides, + ); + + /// Overrides some functions to allow testing. + @visibleForTesting + final XFileTestOverrides? testOverrides; +} + +/// Implementation of [WebXFileCreationParams] with an object url. +@immutable +base class UrlWebXFileCreationParams extends WebXFileCreationParams { + /// Constructs a [UrlWebXFileCreationParams]. + const UrlWebXFileCreationParams({ + required String objectUrl, + @visibleForTesting super.testOverrides, + }) : super(uri: objectUrl); +} + +/// Implementation of [WebXFileCreationParams] with a [Blob]. +@immutable +base class BlobWebXFileCreationParams extends WebXFileCreationParams { + /// Constructs a [BlobWebXFileCreationParams]. + BlobWebXFileCreationParams( + this.blob, { + this.autoRevokeObjectUrl = true, + @visibleForTesting super.testOverrides, + }) : super(uri: URL.createObjectURL(blob)) { + if (autoRevokeObjectUrl) { + _finalizer.attach(this, uri); + } + } + + static final Finalizer _finalizer = Finalizer((String objectUrl) { + URL.revokeObjectURL(objectUrl); + }); + + /// The raw data represented by a [WebXFile]. + final Blob blob; + + /// Whether the object url obtained from [blob] should be revoked when this + /// instance is garbage collected. + final bool autoRevokeObjectUrl; +} + +/// Implementation of [PlatformXFile] for web. +base class WebXFile extends PlatformXFile with WebXFileExtension { + /// Constructs a [WebXFile]. + WebXFile(super.params) : super.implementation(); + + Blob? _cachedBlob; + + @override + PlatformXFileExtension? get extension => this; + + @override + late final WebXFileCreationParams params = + super.params is WebXFileCreationParams + ? super.params as WebXFileCreationParams + : UrlWebXFileCreationParams(objectUrl: super.params.uri); + + @override + Future getBlob() async { + return _cachedBlob ??= switch (params) { + UrlWebXFileCreationParams() => await fetchBlob(params.uri), + final BlobWebXFileCreationParams params => params.blob, + }; + } + + @override + Future canRead() => exists(); + + @override + Future exists() async { + try { + await getBlob(); + return true; + } catch (exception) { + return false; + } + } + + @override + Future lastModified() async { + final Blob blob = await getBlob(); + if (blob is File) { + return DateTime.fromMillisecondsSinceEpoch(blob.lastModified); + } + + return null; + } + + @override + Future length() async { + return (await getBlob()).size; + } + + @override + Stream openRead([int? start, int? end]) async* { + final Blob blob = await getBlob(); + final Blob slice = blob.slice(start ?? 0, end ?? blob.size, blob.type); + yield await blobToBytes(slice); + } + + @override + Future readAsBytes() async { + return blobToBytes(await getBlob()); + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return encoding.decodeStream(openRead()); + } + + @override + Future download([String? suggestedName]) async { + final Blob blob = await getBlob(); + + String? name; + if (suggestedName != null) { + name = suggestedName; + } else if (blob is File) { + name = blob.name; + } + + downloadObjectUrl(params.uri, name, testOverrides: params.testOverrides); + } + + @override + Future name() async { + final Blob blob = await getBlob(); + return blob is File ? blob.name : null; + } +} + +/// Provides platform specific features for [WebXFile]. +mixin WebXFileExtension implements PlatformXFileExtension { + /// The raw data represented by a [WebXFile]. + Future getBlob(); + + /// Attempts to download a [Blob], with [suggestedName] as the filename. + Future download([String? suggestedName]); +} diff --git a/packages/cross_file/cross_file_web/lib/src/web_helpers.dart b/packages/cross_file/cross_file_web/lib/src/web_helpers.dart new file mode 100644 index 000000000000..925afd9f858c --- /dev/null +++ b/packages/cross_file/cross_file_web/lib/src/web_helpers.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 'dart:js_interop'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:web/web.dart'; + +/// Create anchor element with download attribute +HTMLAnchorElement createAnchorElement(String href, String? suggestedName) => + (document.createElement('a') as HTMLAnchorElement) + ..href = href + ..download = suggestedName ?? 'download' + ..rel = 'noreferrer'; + +/// Add an element to a container and click it +void addElementToContainerAndClick(Element container, HTMLElement element) { + // Add the element and click it + // All previous elements will be removed before adding the new one + container.appendChild(element); + element.click(); +} + +/// Initializes a DOM container where elements can be injected. +Element ensureInitialized(String id) { + Element? target = document.querySelector('#$id'); + if (target == null) { + final Element targetElement = document.createElement('flt-x-file')..id = id; + + document.body!.appendChild(targetElement); + target = targetElement; + } + return target; +} + +/// Attempts to download an object (a [Blob]) from its [objectUrl], suggesting [name] as the filename. +/// +/// [name] is a mere *suggestion* for the saved filename; users usually can rename +/// the file to whatever they want before it's actually saved. +/// +/// Maybe some day: https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker +void downloadObjectUrl( + String objectUrl, + String? name, { + XFileTestOverrides? testOverrides, +}) { + // Create a DOM container where the anchor can be injected. + final Element 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 = testOverrides != null + ? testOverrides.createAnchorElement(objectUrl, name) as HTMLAnchorElement + : createAnchorElement(objectUrl, name); + + // Clear the children in target. + while (target.children.length > 0) { + target.removeChild(target.children.item(0)!); + } + + // Add the new `element` and click. + addElementToContainerAndClick(target, element); +} + +/// Converts a [Blob] to [Uint8List] through a [FileReader]. +Future blobToBytes(Blob blob) async { + final reader = FileReader(); + reader.readAsArrayBuffer(blob); + await reader.onLoadEnd.first; + + final Uint8List? result = (reader.result as JSArrayBuffer?)?.toDart + .asUint8List(); + if (result == null) { + throw Exception('Cannot read bytes from Blob. Is it still available?'); + } + + return result; +} + +/// Converts a bunch of [bytes] into a [Blob] and an optional [mimeType]. +Blob bytesToBlob(Uint8List bytes, String? mimeType) { + return Blob( + [bytes.toJS].toJS, + BlobPropertyBag(type: mimeType ?? ''), + ); +} + +/// Retrieves a [Blob] by its [objectUrl]. +Future fetchBlob(String objectUrl) async { + try { + final Response response = await window.fetch(objectUrl.toJS).toDart; + return response.blob().toDart; + } catch (e) { + throw Exception('Could not fetch Blob by URL: $objectUrl'); + } +} + +/// Overrides some functions to allow testing. +// TODO(dit): https://github.com/flutter/flutter/issues/91400 +// Move this to web_helpers_test.dart +class XFileTestOverrides { + /// Default constructor for overrides + XFileTestOverrides({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/cross_file_web/pubspec.yaml b/packages/cross_file/cross_file_web/pubspec.yaml new file mode 100644 index 000000000000..9f10f0c94ebd --- /dev/null +++ b/packages/cross_file/cross_file_web/pubspec.yaml @@ -0,0 +1,37 @@ +name: cross_file_web +description: Implementation of cross_file_platform_interface for web. +repository: https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_web +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 +version: 0.0.1 + +environment: + sdk: ^3.8.0 + flutter: ">=3.32.0" + +flutter: + plugin: + implements: cross_file + platforms: + web: + pluginClass: CrossFileWeb + fileName: cross_file_web.dart + +dependencies: + cross_file_platform_interface: + git: + url: https://github.com/bparrishMines/packages + ref: new_crossfile + path: packages/cross_file/cross_file_platform_interface + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + web: ">=0.5.1 <2.0.0" + +dev_dependencies: + flutter_test: + sdk: flutter + +topics: + - file + - cross-file \ No newline at end of file diff --git a/packages/cross_file/cross_file_web/test/web_cross_file_test.dart b/packages/cross_file/cross_file_web/test/web_cross_file_test.dart new file mode 100644 index 000000000000..d64a68bfa51a --- /dev/null +++ b/packages/cross_file/cross_file_web/test/web_cross_file_test.dart @@ -0,0 +1,206 @@ +// 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. + +@TestOn('chrome') // Uses web-only Flutter SDK +library; + +import 'dart:convert'; +import 'dart:js_interop'; +import 'dart:typed_data'; + +import 'package:cross_file_platform_interface/cross_file_platform_interface.dart'; +import 'package:cross_file_web/cross_file_web.dart'; +import 'package:cross_file_web/src/web_cross_file.dart'; +import 'package:cross_file_web/src/web_helpers.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:web/web.dart' as html; + +const String testFileStringContents = 'Hello, world! I ❤ ñ! 空手'; +final Uint8List testFileBytes = Uint8List.fromList( + utf8.encode(testFileStringContents), +); +final html.File testFile = html.File( + [testFileBytes.toJS].toJS, + 'hello.txt', +); +final String testFileUrl = html.URL.createObjectURL(testFile as JSObject); + +void main() { + group('WebXFile', () { + CrossFilePlatform.instance = CrossFileWeb(); + + group('Create with url', () { + test('openRead', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.openRead().first, testFileBytes); + }); + + test('openRead with partial data', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.openRead(2, 5).first, testFileBytes.sublist(2, 5)); + }); + + test('readAsBytes', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.readAsBytes(), testFileBytes); + }); + + test('readAsString', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.readAsString(), testFileStringContents); + }); + + test('canRead', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.canRead(), true); + }); + + test('exists', () async { + final file = PlatformXFile( + PlatformXFileCreationParams(uri: testFileUrl), + ); + + expect(await file.exists(), true); + }); + }); + + group('Create with File', () { + test('lastModified', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect( + await file.lastModified(), + DateTime.fromMillisecondsSinceEpoch(testFile.lastModified), + ); + }); + + test('length', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.length(), testFile.size); + }); + + test('openRead', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.openRead().first, testFileBytes); + }); + + test('openRead with partial data', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.openRead(2, 5).first, testFileBytes.sublist(2, 5)); + }); + + test('readAsBytes', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.readAsBytes(), testFileBytes); + }); + + test('readAsString', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.readAsString(), testFileStringContents); + }); + + test('canRead', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.canRead(), true); + }); + + test('exists', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.exists(), true); + }); + + test('name', () async { + final file = PlatformXFile(WebXFileCreationParams.fromBlob(testFile)); + + expect(await file.name(), testFile.name); + }); + }); + + group('download', () { + const crossFileDomElementId = '__x_file_dom_element'; + + group('XFile download', () { + test('creates a DOM container', () async { + final file = WebXFile(WebXFileCreationParams.fromBlob(testFile)); + + await file.download(''); + + final html.Element? container = html.document.querySelector( + '#$crossFileDomElementId', + ); + + expect(container, isNotNull); + }); + + test('create anchor element', () async { + final file = WebXFile(WebXFileCreationParams.fromBlob(testFile)); + + await file.download('path'); + + final html.Element container = html.document.querySelector( + '#$crossFileDomElementId', + )!; + + late html.HTMLAnchorElement element; + for (var i = 0; i < container.childNodes.length; i++) { + final html.Element test = container.children.item(i)!; + if (test.tagName == 'A') { + element = test as html.HTMLAnchorElement; + break; + } + } + + // if element is not found, the `firstWhere` call will throw StateError. + expect(element.href, file.params.uri); + expect(element.download, 'path'); + }); + + test('anchor element is clicked', () async { + final mockAnchor = + html.document.createElement('a') as html.HTMLAnchorElement; + + final testOverrides = XFileTestOverrides( + createAnchorElement: (_, __) => mockAnchor, + ); + + final file = WebXFile( + WebXFileCreationParams.fromBlob( + testFile, + testOverrides: testOverrides, + ), + ); + + var clicked = false; + mockAnchor.onClick.listen((html.MouseEvent event) => clicked = true); + + await file.download('path'); + + expect(clicked, true); + }); + }); + }); + }); +} diff --git a/packages/cross_file/dart_test.yaml b/packages/cross_file/dart_test.yaml deleted file mode 100644 index cdb656dbb689..000000000000 --- a/packages/cross_file/dart_test.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#arguments -override_platforms: - chrome: - settings: - executable: chrome - arguments: --no-sandbox diff --git a/packages/cross_file/example/dart_test.yaml b/packages/cross_file/example/dart_test.yaml deleted file mode 100644 index cdb656dbb689..000000000000 --- a/packages/cross_file/example/dart_test.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#arguments -override_platforms: - chrome: - settings: - executable: chrome - arguments: --no-sandbox diff --git a/packages/cross_file/example/lib/readme_excerpts.dart b/packages/cross_file/example/lib/readme_excerpts.dart deleted file mode 100644 index 94e3c879b87c..000000000000 --- a/packages/cross_file/example/lib/readme_excerpts.dart +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -// ignore_for_file: avoid_print - -import 'package:cross_file/cross_file.dart'; - -/// Demonstrate instantiating an XFile for the README. -Future instantiateXFile() async { - // #docregion Instantiate - final file = XFile('assets/hello.txt'); - - print('File information:'); - print('- Path: ${file.path}'); - print('- Name: ${file.name}'); - print('- MIME type: ${file.mimeType}'); - - final String fileContent = await file.readAsString(); - print('Content of the file: $fileContent'); - // #enddocregion Instantiate - - return file; -} diff --git a/packages/cross_file/example/pubspec.yaml b/packages/cross_file/example/pubspec.yaml deleted file mode 100644 index aec48e35e3ce..000000000000 --- a/packages/cross_file/example/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: cross_file_example -description: Demonstrates how to use cross files. -publish_to: none - -environment: - sdk: ^3.8.0 - -dependencies: - cross_file: - # When depending on this package from a real application you should use: - # cross_file: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - -dev_dependencies: - test: ^1.24.0 diff --git a/packages/cross_file/example/test/readme_excerpts_test.dart b/packages/cross_file/example/test/readme_excerpts_test.dart deleted file mode 100644 index f46576cc0fdb..000000000000 --- a/packages/cross_file/example/test/readme_excerpts_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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/cross_file.dart'; -import 'package:cross_file_example/readme_excerpts.dart'; -import 'package:test/test.dart'; - -const bool kIsWeb = bool.fromEnvironment('dart.library.js_interop'); - -void main() { - test('instantiateXFile loads asset file', () async { - // Ensure that the snippet code runs successfully. - final XFile xFile = await instantiateXFile(); - // It should have a nonempty path and name. - expect(xFile.path, allOf(isNotNull, isNotEmpty)); - expect(xFile.name, allOf(isNotNull, isNotEmpty)); - - // And the example file should have contents. - final String fileContent = await xFile.readAsString(); - expect(fileContent, allOf(isNotNull, isNotEmpty)); - }, skip: kIsWeb); -} diff --git a/packages/cross_file/lib/src/types/base.dart b/packages/cross_file/lib/src/types/base.dart deleted file mode 100644 index b3fb82ea4063..000000000000 --- a/packages/cross_file/lib/src/types/base.dart +++ /dev/null @@ -1,89 +0,0 @@ -// 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'; - -/// The interface for a CrossFile. -/// -/// A CrossFile 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 CrossFile - // ignore: avoid_unused_constructor_parameters - XFileBase(String? path); - - /// Save the CrossFile at the indicated file path. - Future saveTo(String path) { - 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 CrossFile 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. - /// - /// For non-web implementation, this represents the last part of the filesystem path. - /// - /// 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.'); - } - - /// Asynchronously 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.'); - } - - /// Asynchronously 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 CrossFile - Future lastModified() { - throw UnimplementedError('lastModified() has not been implemented.'); - } -} diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart deleted file mode 100644 index 40703aa754fc..000000000000 --- a/packages/cross_file/lib/src/types/html.dart +++ /dev/null @@ -1,195 +0,0 @@ -// 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 'dart:convert'; -import 'dart:js_interop'; -import 'dart:typed_data'; - -import 'package:web/web.dart'; - -import '../web_helpers/web_helpers.dart'; -import 'base.dart'; - -// Four Gigabytes, in bytes. -const int _fourGigabytes = 4 * 1024 * 1024 * 1024; - -/// A CrossFile that works on web. -/// -/// It wraps the bytes of a selected file. -class XFile extends XFileBase { - /// Construct a CrossFile 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 it's only available - /// while handling [html.File]s (when the ObjectUrl is created). - // ignore: use_super_parameters - XFile( - String path, { - String? mimeType, - String? name, - int? length, - Uint8List? bytes, - DateTime? lastModified, - }) : _mimeType = mimeType, - _path = path, - _length = length, - _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), - _name = name ?? '', - super(path) { - // Cache `bytes` as Blob, if passed. - if (bytes != null) { - _browserBlob = _createBlobFromBytes(bytes, mimeType); - } - } - - /// Construct an CrossFile from its data - XFile.fromData( - Uint8List bytes, { - String? mimeType, - String? name, - int? length, - DateTime? lastModified, - String? path, - }) : _mimeType = mimeType, - _length = length, - _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), - _name = name ?? '', - super(path) { - _browserBlob = _createBlobFromBytes(bytes, mimeType); - _path = URL.createObjectURL(_browserBlob!); - } - - // Initializes a Blob from a bunch of `bytes` and an optional `mimeType`. - Blob _createBlobFromBytes(Uint8List bytes, String? mimeType) { - return (mimeType == null) - ? Blob([bytes.toJS].toJS) - : Blob( - [bytes.toJS].toJS, - BlobPropertyBag(type: mimeType), - ); - } - - // Overridable (meta) data that can be specified by the constructors. - - // 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; - - // The link to the binary object in the browser memory (Blob). - // This can be passed in (as `bytes` in the constructor) or derived from - // [_path] with a fetch request. - // (Similar to a (read-only) dart:io File.) - Blob? _browserBlob; - - @override - String? get mimeType => _mimeType; - - @override - String get name => _name; - - @override - String get path => _path; - - @override - Future lastModified() async => _lastModified; - - Future get _blob async { - if (_browserBlob != null) { - return _browserBlob!; - } - - // Attempt to re-hydrate the blob from the `path` via a (local) HttpRequest. - // Note that safari hangs if the Blob is >=4GB, so bail out in that case. - if (isSafari() && _length != null && _length >= _fourGigabytes) { - throw Exception('Safari cannot handle XFiles larger than 4GB.'); - } - - final blobCompleter = Completer(); - - late XMLHttpRequest request; - request = XMLHttpRequest() - ..open('get', path, true) - ..responseType = 'blob' - ..onLoad.listen((ProgressEvent e) { - assert( - request.response != null, - 'The Blob backing this XFile cannot be null!', - ); - blobCompleter.complete(request.response! as Blob); - }) - ..onError.listen((ProgressEvent e) { - if (e.type == 'error') { - blobCompleter.completeError( - Exception('Could not load Blob from its URL. Has it been revoked?'), - ); - } - }) - ..send(); - - return blobCompleter.future; - } - - @override - Future readAsBytes() async { - return _blob.then(_blobToByteBuffer); - } - - @override - Future length() async => _length ?? (await _blob).size; - - @override - Future readAsString({Encoding encoding = utf8}) async { - return readAsBytes().then(encoding.decode); - } - - // TODO(dit): https://github.com/flutter/flutter/issues/91867 Implement openRead properly. - @override - Stream openRead([int? start, int? end]) async* { - final Blob blob = await _blob; - - final Blob slice = blob.slice(start ?? 0, end ?? blob.size, blob.type); - - final Uint8List convertedSlice = await _blobToByteBuffer(slice); - - yield convertedSlice; - } - - // Converts an html Blob object to a Uint8List, through a FileReader. - Future _blobToByteBuffer(Blob blob) async { - final reader = FileReader(); - reader.readAsArrayBuffer(blob); - - await reader.onLoadEnd.first; - - final Uint8List? result = (reader.result as JSArrayBuffer?)?.toDart - .asUint8List(); - - if (result == null) { - throw Exception('Cannot read bytes from Blob. Is it still available?'); - } - - return result; - } - - /// Saves the data of this CrossFile at the location indicated by path. - /// For the web implementation, the path variable is ignored. - @override - Future saveTo(String path) async { - await saveFileAs(this); - } -} diff --git a/packages/cross_file/lib/src/types/interface.dart b/packages/cross_file/lib/src/types/interface.dart deleted file mode 100644 index d539b4e2633c..000000000000 --- a/packages/cross_file/lib/src/types/interface.dart +++ /dev/null @@ -1,65 +0,0 @@ -// 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:typed_data'; -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. -class XFile extends XFileBase { - /// Construct a CrossFile 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 super.path, { - String? mimeType, - String? name, - int? length, - Uint8List? bytes, - DateTime? lastModified, - @visibleForTesting CrossFileTestOverrides? overrides, - }) { - throw UnimplementedError( - 'CrossFile is not available in your current platform.', - ); - } - - /// Construct a CrossFile object from its data. - /// - /// On the web, the [path] is ignored if the [bytes] are provided, - /// as the underlying Blob URL is used as the path. - XFile.fromData( - Uint8List bytes, { - String? mimeType, - String? name, - int? length, - DateTime? lastModified, - String? path, - @visibleForTesting CrossFileTestOverrides? overrides, - }) : super(path) { - throw UnimplementedError( - 'CrossFile is not available in your current platform.', - ); - } -} - -/// Overrides some functions of CrossFile for testing purposes -@visibleForTesting -class CrossFileTestOverrides { - /// Default constructor for overrides - CrossFileTestOverrides({required this.createAnchorElement}); - - /// For overriding the creation of the file input element. - dynamic Function(String href, String suggestedName) createAnchorElement; -} diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart deleted file mode 100644 index c1931a743a89..000000000000 --- a/packages/cross_file/lib/src/types/io.dart +++ /dev/null @@ -1,141 +0,0 @@ -// 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:io'; -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 { - /// Construct a CrossFile object backed by a dart:io File. - /// - /// [bytes] is ignored; the parameter exists only to match the web version of - /// the constructor. To construct a dart:io XFile from bytes, use - /// [XFile.fromData]. - /// - /// [name] is ignored; the parameter exists only to match the web version of - /// the constructor. - /// - /// [length] is ignored; the parameter exists only to match the web version of - /// the constructor. - /// - // ignore: use_super_parameters - XFile( - String path, { - String? mimeType, - String? name, - int? length, - Uint8List? bytes, - DateTime? lastModified, - }) : _mimeType = mimeType, - _file = File(path), - _bytes = null, - _lastModified = lastModified, - super(path); - - /// Construct an CrossFile from its data - /// - /// [name] is ignored; the parameter exists only to match the web version of - /// the constructor. - XFile.fromData( - Uint8List bytes, { - String? mimeType, - String? path, - String? name, - int? length, - DateTime? lastModified, - }) : _mimeType = mimeType, - _bytes = bytes, - _file = File(path ?? ''), - _length = length, - _lastModified = lastModified, - super(path) { - if (length == null) { - _length = bytes.length; - } - } - - final File _file; - final String? _mimeType; - final DateTime? _lastModified; - int? _length; - - final Uint8List? _bytes; - - @override - Future lastModified() { - if (_lastModified != null) { - return Future.value(_lastModified); - } - // ignore: avoid_slow_async_io - return _file.lastModified(); - } - - @override - Future saveTo(String path) async { - if (_bytes == null) { - await _file.copy(path); - } else { - final fileToSave = File(path); - // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 - // ignore: unnecessary_non_null_assertion - await fileToSave.writeAsBytes(_bytes!); - } - } - - @override - String? get mimeType => _mimeType; - - @override - String get path => _file.path; - - @override - String get name => _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) { - // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 - // ignore: unnecessary_non_null_assertion - return Future.value(String.fromCharCodes(_bytes!)); - } - return _file.readAsString(encoding: encoding); - } - - @override - Future readAsBytes() { - if (_bytes != null) { - return Future.value(_bytes); - } - return _file.readAsBytes(); - } - - Stream _getBytes(int? start, int? end) async* { - final Uint8List bytes = _bytes!; - yield bytes.sublist(start ?? 0, end ?? bytes.length); - } - - @override - Stream openRead([int? start, int? end]) { - if (_bytes != null) { - return _getBytes(start, end); - } else { - return _file - .openRead(start ?? 0, end) - .map((List chunk) => Uint8List.fromList(chunk)); - } - } -} diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart deleted file mode 100644 index 58231dd8c30e..000000000000 --- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart +++ /dev/null @@ -1,70 +0,0 @@ -// 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:meta/meta.dart'; -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); - -/// Create anchor element with download attribute -HTMLAnchorElement _createAnchorElementImpl( - String href, - String? suggestedName, -) => (document.createElement('a') as HTMLAnchorElement) - ..href = href - ..download = suggestedName ?? 'download'; - -/// Function for creating anchor elements. Can be overridden for testing. -@visibleForTesting -CreateAnchorElement createAnchorElementFunction = _createAnchorElementImpl; - -/// Add an element to a container and click it -void addElementToContainerAndClick(Element container, HTMLElement element) { - // Add the element and click it - // All previous elements will be removed before adding the new one - container.appendChild(element); - element.click(); -} - -/// Initializes a DOM container where elements can be injected. -Element ensureInitialized(String id) { - Element? target = document.querySelector('#$id'); - if (target == null) { - final Element targetElement = document.createElement('flt-x-file')..id = id; - - document.body!.appendChild(targetElement); - target = targetElement; - } - return target; -} - -/// Determines if the browser is Safari from its vendor string. -/// (This is the same check used in flutter/engine) -bool isSafari() { - return window.navigator.vendor == 'Apple Computer, Inc.'; -} - -/// Saves the given [XFile] to user's device ("Save As" dialog). -Future saveFileAs(XFile file) async { - // Create container element. - final Element target = ensureInitialized('__x_file_dom_element'); - - // Create element. - final HTMLAnchorElement element = createAnchorElementFunction( - 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 deleted file mode 100644 index dedcc19f8ae5..000000000000 --- a/packages/cross_file/pubspec.yaml +++ /dev/null @@ -1,19 +0,0 @@ -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+2 - -environment: - sdk: ^3.8.0 - -dependencies: - meta: ^1.3.0 - web: ">=0.5.1 <2.0.0" - -dev_dependencies: - path: ^1.8.1 - test: ^1.21.1 - -topics: - - files diff --git a/packages/cross_file/test/assets/hello.txt b/packages/cross_file/test/assets/hello.txt deleted file mode 100644 index 5dd01c177f5d..000000000000 --- a/packages/cross_file/test/assets/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, world! \ No newline at end of file diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart deleted file mode 100644 index e57e36c285c7..000000000000 --- a/packages/cross_file/test/x_file_html_test.dart +++ /dev/null @@ -1,173 +0,0 @@ -// 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. - -@TestOn('chrome') // Uses web-only Flutter SDK -library; - -import 'dart:convert'; -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; - -const String expectedStringContents = 'Hello, world! I ❤ ñ! 空手'; -final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); -final html.File textFile = html.File( - [bytes.toJS].toJS, - 'hello.txt', -); -final String textFileUrl = - // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3 - // ignore: unnecessary_cast - html.URL.createObjectURL(textFile as JSObject); - -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))); - }); - }); - - 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))); - }); - - test('Prefers local bytes over path if both are provided', () async { - const text = 'Hello World'; - const path = 'test/x_file_html_test.dart'; - - final file = XFile.fromData( - utf8.encode(text), - path: path, - name: 'x_file_html_test.dart', - length: text.length, - mimeType: 'text/plain', - lastModified: DateTime.now(), - ); - - expect(file.path, isNot(equals(path))); - expect(file.path.startsWith('blob:'), isTrue); - expect(await file.readAsString(), equals(text)); - }); - }); - - group('Blob backend', () { - final file = XFile(textFileUrl); - - test('Stores data as a Blob', () async { - // Read the blob from its path 'natively' - final html.Response response = await html.window - .fetch(file.path.toJS) - .toDart; - - final JSAny arrayBuffer = await response.arrayBuffer().toDart; - final ByteBuffer data = (arrayBuffer as JSArrayBuffer).toDart; - expect(data.asUint8List(), equals(bytes)); - }); - - test('Data may be purged from the blob!', () async { - html.URL.revokeObjectURL(file.path); - - expect(() async { - await file.readAsBytes(); - }, throwsException); - }); - }); - - group('saveTo(..)', () { - const crossFileDomElementId = '__x_file_dom_element'; - - group('CrossFile saveTo(..)', () { - test('creates a DOM container', () async { - final file = XFile.fromData(bytes); - - await file.saveTo(''); - - final html.Element? container = html.document.querySelector( - '#$crossFileDomElementId', - ); - - expect(container, isNotNull); - }); - - test('create anchor element', () async { - final file = XFile.fromData(bytes, name: textFile.name); - - await file.saveTo('path'); - - final html.Element container = html.document.querySelector( - '#$crossFileDomElementId', - )!; - - late html.HTMLAnchorElement element; - for (var i = 0; i < container.childNodes.length; i++) { - final html.Element test = container.children.item(i)!; - if (test.tagName == 'A') { - element = test as html.HTMLAnchorElement; - break; - } - } - - // 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.document.createElement('a') as html.HTMLAnchorElement; - - // Save original function so we can restore it - final helpers.CreateAnchorElement original = - helpers.createAnchorElementFunction; - - addTearDown(() { - helpers.createAnchorElementFunction = original; - }); - helpers.createAnchorElementFunction = (_, __) => mockAnchor; - - final file = XFile.fromData(bytes, name: textFile.name); - - var clicked = false; - mockAnchor.onClick.listen((html.MouseEvent event) => clicked = true); - - await file.saveTo('path'); - - expect(clicked, true); - }); - }); - }); -} diff --git a/packages/cross_file/test/x_file_io_test.dart b/packages/cross_file/test/x_file_io_test.dart deleted file mode 100644 index 904a5c08c507..000000000000 --- a/packages/cross_file/test/x_file_io_test.dart +++ /dev/null @@ -1,134 +0,0 @@ -// 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. - -@TestOn('vm') // Uses dart:io -library; - -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:cross_file/cross_file.dart'; -import 'package:test/test.dart'; - -final String pathPrefix = Directory.current.path.endsWith('test') - ? './assets/' - : './test/assets/'; -final String path = '${pathPrefix}hello.txt'; -const String expectedStringContents = 'Hello, world!'; -final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); -final File textFile = File(path); -final String textFilePath = textFile.path; - -void main() { - group('Create with a path', () { - test('Can be read as a string', () async { - final file = XFile(textFilePath); - expect(await file.readAsString(), equals(expectedStringContents)); - }); - test('Can be read as bytes', () async { - final file = XFile(textFilePath); - expect(await file.readAsBytes(), equals(bytes)); - }); - - test('Can be read as a stream', () async { - final file = XFile(textFilePath); - expect(await file.openRead().first, equals(bytes)); - }); - - test('Stream can be sliced', () async { - final file = XFile(textFilePath); - expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); - }); - - test('saveTo(..) creates file', () async { - final file = XFile(textFilePath); - final Directory tempDir = Directory.systemTemp.createTempSync(); - final targetFile = File('${tempDir.path}/newFilePath.txt'); - if (targetFile.existsSync()) { - await targetFile.delete(); - } - - await file.saveTo(targetFile.path); - - expect(targetFile.existsSync(), isTrue); - expect(targetFile.readAsStringSync(), 'Hello, world!'); - - await tempDir.delete(recursive: true); - }); - - test('saveTo(..) does not load the file into memory', () async { - final file = TestXFile(textFilePath); - final Directory tempDir = Directory.systemTemp.createTempSync(); - final targetFile = File('${tempDir.path}/newFilePath.txt'); - if (targetFile.existsSync()) { - await targetFile.delete(); - } - - await file.saveTo(targetFile.path); - - expect(file.hasBeenRead, isFalse); - - await tempDir.delete(recursive: true); - }); - - test('nullability is correct', () async { - expect(_ensureNonnullPathArgument('a/path'), isNotNull); - }); - }); - - 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 { - final Directory tempDir = Directory.systemTemp.createTempSync(); - final targetFile = File('${tempDir.path}/newFilePath.txt'); - if (targetFile.existsSync()) { - await targetFile.delete(); - } - - await file.saveTo(targetFile.path); - - expect(targetFile.existsSync(), isTrue); - expect(targetFile.readAsStringSync(), 'Hello, world!'); - - await tempDir.delete(recursive: true); - }); - }); -} - -// This is to create an analysis error if the version of XFile in -// interface.dart, which should never actually be used but is what the analyzer -// runs against, has the nullability of `path` changed. -XFile _ensureNonnullPathArgument(String? path) { - return XFile(path!); -} - -/// An XFile subclass that tracks reads, for testing purposes. -class TestXFile extends XFile { - TestXFile(super.path); - - bool hasBeenRead = false; - - @override - Future readAsBytes() { - hasBeenRead = true; - return super.readAsBytes(); - } -}