diff --git a/.ci.yaml b/.ci.yaml index 0b277d28053b..93e3d4035346 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1472,6 +1472,7 @@ targets: - name: Windows_arm64 windows-build_all_packages master recipe: packages/packages + presubmit: false timeout: 30 bringup: true # https://github.com/flutter/flutter/issues/134083 properties: @@ -1506,6 +1507,7 @@ targets: - name: Windows_arm64 windows-build_all_packages stable recipe: packages/packages + presubmit: false timeout: 30 bringup: true properties: diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 359ddb64593e..ac3ccc411fdc 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -064c340baf0e23790374f5b34ea067c2478e7fd1 +d12ba5c270d83c63cd3c1e89c1cd1f279bbb5696 diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index 845fae768cba..304f1de6baf4 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -bae5e49bc2a867403c43b2aae2de8f8c33b037e4 +300451adae589accbece3490f4396f10bdf15e6e diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d9157022847f..d1f55572e699 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -395,6 +395,34 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "gradle" + directory: "/packages/interactive_media_ads/android" + commit-message: + prefix: "[interactive_media_ads]" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "com.android.tools.build:gradle" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - dependency-name: "junit:junit" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - dependency-name: "org.mockito:*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - dependency-name: "androidx.test:*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + + - package-ecosystem: "gradle" + directory: "/packages/interactive_media_ads/example/android/app" + commit-message: + prefix: "[interactive_media_ads]" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "gradle" directory: "/packages/image_picker/image_picker/example/android/app" commit-message: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adf983fdc5e5..777e15bcc6e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,13 +36,20 @@ jobs: cd $GITHUB_WORKSPACE # Checks out a copy of the repo. - name: Check out code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 with: fetch-depth: 0 # Fetch all history so the tool can get all the tags to determine version. - name: Set up tools run: dart pub get working-directory: ${{ github.workspace }}/script/tool + # Give some time for LUCI checks to start becoming populated. + # Because of latency in Github Webhooks, we need to wait for a while + # before being able to look at checks scheduled by LUCI. + - name: Give webhooks a minute + run: sleep 60s + shell: bash + # The next step waits for all tests, but when there are issues with the # hooks it can take a long time for the tests to even be registered. If # "Wait on all tests" runs before that happens, it will pass immediately diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 6f3299ad195d..2cf05fe701cd 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -21,7 +21,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v2.4.0 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v2.4.0 with: persist-credentials: false @@ -49,6 +49,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v1.0.26 + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v1.0.26 with: sarif_file: results.sarif diff --git a/CODEOWNERS b/CODEOWNERS index 7ccddb125462..77f7adb749f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -23,6 +23,7 @@ packages/google_identity_services_web/** @ditman packages/google_maps_flutter/** @stuartmorgan packages/google_sign_in/** @stuartmorgan packages/image_picker/** @tarrinneal +packages/interactive_media_ads/** @bparrishMines packages/in_app_purchase/** @bparrishMines packages/local_auth/** @stuartmorgan packages/metrics_center/** @keyonghan @@ -88,8 +89,9 @@ packages/google_sign_in/google_sign_in_ios/** @vashworth packages/image_picker/image_picker_ios/** @vashworth packages/in_app_purchase/in_app_purchase_storekit/** @louisehsu packages/ios_platform_images/** @jmagman -packages/local_auth/local_auth_darwin/** @louisehsu +packages/local_auth/local_auth_darwin/** @louisehsu packages/path_provider/path_provider_foundation/** @jmagman +packages/pigeon/**/ios/**/* @hellohuanlin packages/pointer_interceptor/pointer_interceptor_ios/** @ditman packages/quick_actions/quick_actions_ios/** @hellohuanlin packages/shared_preferences/shared_preferences_foundation/** @tarrinneal diff --git a/README.md b/README.md index ac0078ff5f72..1996143171fb 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ These are the packages hosted in this repository: | [google\_maps\_flutter](./packages/google_maps_flutter/) | [![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dev/packages/google_maps_flutter) | [![pub points](https://img.shields.io/pub/points/google_maps_flutter)](https://pub.dev/packages/google_maps_flutter/score) | [![popularity](https://img.shields.io/pub/popularity/google_maps_flutter)](https://pub.dev/packages/google_maps_flutter/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20maps?label=)](https://github.com/flutter/flutter/labels/p%3A%20maps) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20google_maps_flutter?label=)](https://github.com/flutter/packages/labels/p%3A%20google_maps_flutter) | | [google\_sign\_in](./packages/google_sign_in/) | [![pub package](https://img.shields.io/pub/v/google_sign_in.svg)](https://pub.dev/packages/google_sign_in) | [![pub points](https://img.shields.io/pub/points/google_sign_in)](https://pub.dev/packages/google_sign_in/score) | [![popularity](https://img.shields.io/pub/popularity/google_sign_in)](https://pub.dev/packages/google_sign_in/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20google_sign_in?label=)](https://github.com/flutter/flutter/labels/p%3A%20google_sign_in) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20google_sign_in?label=)](https://github.com/flutter/packages/labels/p%3A%20google_sign_in) | | [image\_picker](./packages/image_picker/) | [![pub package](https://img.shields.io/pub/v/image_picker.svg)](https://pub.dev/packages/image_picker) | [![pub points](https://img.shields.io/pub/points/image_picker)](https://pub.dev/packages/image_picker/score) | [![popularity](https://img.shields.io/pub/popularity/image_picker)](https://pub.dev/packages/image_picker/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20image_picker?label=)](https://github.com/flutter/flutter/labels/p%3A%20image_picker) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20image_picker?label=)](https://github.com/flutter/packages/labels/p%3A%20image_picker) | +| [interactive\_media\_ads](./packages/interactive_media_ads/) | [![pub package](https://img.shields.io/pub/v/interactive_media_ads.svg)](https://pub.dev/packages/interactive_media_ads) | [![pub points](https://img.shields.io/pub/points/interactive_media_ads)](https://pub.dev/packages/interactive_media_ads/score) | [![popularity](https://img.shields.io/pub/popularity/interactive_media_ads)](https://pub.dev/packages/interactive_media_ads/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20interactive_media_ads?label=)](https://github.com/flutter/flutter/labels/p%3A%20interactive_media_ads) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20interactive_media_ads?label=)](https://github.com/flutter/packages/labels/p%3A%20interactive_media_ads) | | [in\_app\_purchase](./packages/in_app_purchase/) | [![pub package](https://img.shields.io/pub/v/in_app_purchase.svg)](https://pub.dev/packages/in_app_purchase) | [![pub points](https://img.shields.io/pub/points/in_app_purchase)](https://pub.dev/packages/in_app_purchase/score) | [![popularity](https://img.shields.io/pub/popularity/in_app_purchase)](https://pub.dev/packages/in_app_purchase/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20in_app_purchase?label=)](https://github.com/flutter/flutter/labels/p%3A%20in_app_purchase) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20in_app_purchase?label=)](https://github.com/flutter/packages/labels/p%3A%20in_app_purchase) | | [ios\_platform\_images](./packages/ios_platform_images/) | [![pub package](https://img.shields.io/pub/v/ios_platform_images.svg)](https://pub.dev/packages/ios_platform_images) | [![pub points](https://img.shields.io/pub/points/ios_platform_images)](https://pub.dev/packages/ios_platform_images/score) | [![popularity](https://img.shields.io/pub/popularity/ios_platform_images)](https://pub.dev/packages/ios_platform_images/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20ios_platform_images?label=)](https://github.com/flutter/flutter/labels/p%3A%20ios_platform_images) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20ios_platform_images?label=)](https://github.com/flutter/packages/labels/p%3A%20ios_platform_images) | | [local\_auth](./packages/local_auth/) | [![pub package](https://img.shields.io/pub/v/local_auth.svg)](https://pub.dev/packages/local_auth) | [![pub points](https://img.shields.io/pub/points/local_auth)](https://pub.dev/packages/local_auth/score) | [![popularity](https://img.shields.io/pub/popularity/local_auth)](https://pub.dev/packages/local_auth/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20local_auth?label=)](https://github.com/flutter/flutter/labels/p%3A%20local_auth) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20local_auth?label=)](https://github.com/flutter/packages/labels/p%3A%20local_auth) | diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index d7bbc2bdecd5..82d27a401e85 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,7 +1,12 @@ -## NEXT +## 0.10.8+18 + +* Updates annotations lib to 1.7.1. + +## 0.10.8+17 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. +* Updates `README.md` to encourage developers to opt into `camera_android_camerax`. ## 0.10.8+16 diff --git a/packages/camera/camera_android/README.md b/packages/camera/camera_android/README.md index 509d5b880226..31f2d66ae4e4 100644 --- a/packages/camera/camera_android/README.md +++ b/packages/camera/camera_android/README.md @@ -2,6 +2,12 @@ The Android implementation of [`camera`][1]. +*Note*: [`camera_android_camerax`][3] will become the default implementation of +`camera` on Android by May 2024, so **we strongly encourage you to opt into it** +by using [these instructions][4]. If any [limitations][5] of `camera_android_camerax` +prevent you from using it or if you run into any problems, please report these +issues under [`flutter/flutter`][5] with `[camerax]` in the title. + ## Usage This package is [endorsed][2], which means you can simply use `camera` @@ -13,3 +19,6 @@ should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/camera [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[3]: https://pub.dev/packages/camera_android_camerax +[4]: https://pub.dev/packages/camera_android_camerax#usage +[5]: https://pub.dev/packages/camera_android_camerax#limitations diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index 824931536ec8..dfa21b72bb86 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -65,7 +65,7 @@ buildFeatures { } dependencies { - implementation 'androidx.annotation:annotation:1.7.0' + implementation 'androidx.annotation:annotation:1.7.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 48a04d46ef30..e45547bc0a92 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.8+16 +version: 0.10.8+18 environment: sdk: ^3.1.0 diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ce4475ebf0fc..83ad40f8f5b5 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,5 +1,38 @@ -## NEXT +## 0.6.2 +* Move integration_test dependency to test. + +## 0.6.1 + +* Modifies resolution selection logic to use an `AspectRatioStrategy` for all aspect ratios supported by CameraX. +* Adds `ResolutionFilter` to resolution selection logic to prioritize resolutions that match + the defined `ResolutionPreset`s. + +## 0.6.0+1 + +* Updates `README.md` to encourage developers to opt into this implementation of the camera plugin. + +## 0.6.0 + +* Implements `setFocusMode`, which makes this plugin reach feature parity with camera_android. +* Fixes `setExposureCompensationIndex` return value to use index returned by CameraX. + +## 0.5.0+36 + +* Implements `setExposureMode`. + +## 0.5.0+35 + +* Modifies `CameraInitializedEvent` that is sent when the camera is initialized to indicate that the initial focus + and exposure modes are auto and that developers may set focus and exposure points. + +## 0.5.0+34 + +* Implements `setFocusPoint`, `setExposurePoint`, and `setExposureOffset`. + +## 0.5.0+33 + +* Fixes typo in `README.md`. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 0.5.0+32 diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 3a2e49d7e60d..64a56f1a3b54 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -2,28 +2,24 @@ An Android implementation of [`camera`][1] that uses the [CameraX library][2]. -*Note*: This package is under development, so please note the -[missing features and limitations](#missing-features-and-limitations), but -otherwise feel free to try out the current implementation and provide any -feedback by filing issues under [`flutter/flutter`][5] with `[camerax]` in -the title, which will be actively triaged. +*Note*: This implementation will become the default implementation of `camera` +on Android by May 2024, so **we strongly encourage you to opt into it** +by using [the instructions](#usage) below. If any of [the limitations](#limitations) +prevent you from using `camera_android_camerax` or if you run into any problems, +please report these issues under [`flutter/flutter`][5] with `[camerax]` in +the title. ## Usage -This package is [non-endorsed][3]; the endorsed Android implementation of `camera` -is [`camera_android`][4]. To use this implementation of the plugin instead of -`camera_android`, you will need to specify it in your `pubsepc.yaml` file as a -dependency in addition to `camera`: +To use this plugin instead of [`camera_android`][4], run -```yaml -dependencies: - # ...along with your other dependencies - camera: ^0.10.4 - camera_android_camerax: ^0.5.0 +```sh +$ flutter pub add camera_android_camerax ``` -## Missing features and limitations +from your project's root directory. +## Limitations ### 240p resolution configuration for video recording @@ -31,14 +27,6 @@ dependencies: and thus, the plugin will fall back to 480p if configured with a `ResolutionPreset`. -### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\] - -`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented. - -### Focus mode & point configuration \[[Issue #120467][120467]\] - -`setFocusMode` & `setFocusPoint` are unimplemented. - ### Setting maximum duration and stream options for video capture Calling `startVideoCapturing` with `VideoCaptureOptions` configured with diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index b0e0493cbe2a..d281bbe65a8c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -27,6 +27,10 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity @VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl; @VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl; @VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl; + @VisibleForTesting @Nullable public MeteringPointHostApiImpl meteringPointHostApiImpl; + + @VisibleForTesting @Nullable + public Camera2CameraControlHostApiImpl camera2CameraControlHostApiImpl; @VisibleForTesting public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl; @@ -119,6 +123,19 @@ public void setUp( cameraControlHostApiImpl = new CameraControlHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl); + camera2CameraControlHostApiImpl = new Camera2CameraControlHostApiImpl(instanceManager, context); + GeneratedCameraXLibrary.Camera2CameraControlHostApi.setup( + binaryMessenger, camera2CameraControlHostApiImpl); + GeneratedCameraXLibrary.CaptureRequestOptionsHostApi.setup( + binaryMessenger, new CaptureRequestOptionsHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup( + binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup( + binaryMessenger, new FocusMeteringResultHostApiImpl(instanceManager)); + meteringPointHostApiImpl = new MeteringPointHostApiImpl(instanceManager); + GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, meteringPointHostApiImpl); + GeneratedCameraXLibrary.ResolutionFilterHostApi.setup( + binaryMessenger, new ResolutionFilterHostApiImpl(instanceManager)); } @Override @@ -210,6 +227,9 @@ public void updateContext(@NonNull Context context) { if (cameraControlHostApiImpl != null) { cameraControlHostApiImpl.setContext(context); } + if (camera2CameraControlHostApiImpl != null) { + camera2CameraControlHostApiImpl.setContext(context); + } } /** Sets {@code LifecycleOwner} that is used to control the lifecycle of the camera by CameraX. */ @@ -238,5 +258,8 @@ public void updateActivity(@Nullable Activity activity) { if (deviceOrientationManagerHostApiImpl != null) { deviceOrientationManagerHostApiImpl.setActivity(activity); } + if (meteringPointHostApiImpl != null) { + meteringPointHostApiImpl.setActivity(activity); + } } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java index ba26e91c9622..ba4318a8927f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java @@ -83,6 +83,12 @@ public void onSuccess(Void voidResult) { } public void onFailure(Throwable t) { + if (t instanceof CameraControl.OperationCanceledException) { + // Operation was canceled due to camera being closed or a new request was submitted, which + // is not actionable and should not block a new value from potentially being submitted. + result.success(null); + return; + } result.error(t); } }, @@ -94,6 +100,9 @@ public void onFailure(Throwable t) { * *

Will trigger an auto focus action and enable auto focus/auto exposure/auto white balance * metering regions. + * + *

Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was + * canceled. */ public void startFocusAndMetering( @NonNull CameraControl cameraControl, @@ -117,6 +126,12 @@ public void onSuccess(FocusMeteringResult focusMeteringResult) { } public void onFailure(Throwable t) { + if (t instanceof CameraControl.OperationCanceledException) { + // Operation was canceled due to camera being closed or a new request was submitted, which + // is not actionable and should not block a new value from potentially being submitted. + result.success(null); + return; + } result.error(t); } }, @@ -152,6 +167,9 @@ public void onFailure(Throwable t) { *

The exposure compensation value set on the camera must be within the range of {@code * ExposureState#getExposureCompensationRange()} for the current {@code ExposureState} for the * call to succeed. + * + *

Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was + * canceled. */ public void setExposureCompensationIndex( @NonNull CameraControl cameraControl, @NonNull Long index, @NonNull Result result) { @@ -166,6 +184,12 @@ public void onSuccess(Integer integerResult) { } public void onFailure(Throwable t) { + if (t instanceof CameraControl.OperationCanceledException) { + // Operation was canceled due to camera being closed or a new request was submitted, which + // is not actionable and should not block a new value from potentially being submitted. + result.success(null); + return; + } result.error(t); } }, diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java index 6f8d8c91dda6..f76dd5422e72 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java @@ -110,8 +110,8 @@ public void create(@NonNull Long identifier, @NonNull Map options) Map decodedOptions = new HashMap(); for (Map.Entry option : options.entrySet()) { - decodedOptions.put( - CaptureRequestKeySupportedType.values()[option.getKey().intValue()], option.getValue()); + Integer index = ((Number) option.getKey()).intValue(); + decodedOptions.put(CaptureRequestKeySupportedType.values()[index], option.getValue()); } instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java index 5eeedd15211c..dcda333c2e90 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java @@ -5,6 +5,7 @@ package io.flutter.plugins.camerax; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.camera.core.FocusMeteringAction; import androidx.camera.core.MeteringPoint; @@ -29,7 +30,9 @@ public class FocusMeteringActionHostApiImpl implements FocusMeteringActionHostAp public static class FocusMeteringActionProxy { /** Creates an instance of {@link FocusMeteringAction}. */ public @NonNull FocusMeteringAction create( - @NonNull List meteringPoints, @NonNull List meteringPointModes) { + @NonNull List meteringPoints, + @NonNull List meteringPointModes, + @Nullable Boolean disableAutoCancel) { if (meteringPoints.size() >= 1 && meteringPoints.size() != meteringPointModes.size()) { throw new IllegalArgumentException( "One metering point must be specified and the number of specified metering points must match the number of specified metering point modes."); @@ -59,6 +62,10 @@ public static class FocusMeteringActionProxy { } } + if (disableAutoCancel != null && disableAutoCancel == true) { + focusMeteringActionBuilder.disableAutoCancel(); + } + return focusMeteringActionBuilder.build(); } @@ -100,7 +107,9 @@ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager) @Override public void create( - @NonNull Long identifier, @NonNull List meteringPointInfos) { + @NonNull Long identifier, + @NonNull List meteringPointInfos, + @Nullable Boolean disableAutoCancel) { final List meteringPoints = new ArrayList(); final List meteringPointModes = new ArrayList(); for (MeteringPointInfo meteringPointInfo : meteringPointInfos) { @@ -110,6 +119,6 @@ public void create( } instanceManager.addDartCreatedInstance( - proxy.create(meteringPoints, meteringPointModes), identifier); + proxy.create(meteringPoints, meteringPointModes, disableAutoCancel), identifier); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index 08f8ea2da9ff..fa6be7920961 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -83,14 +83,14 @@ private CameraStateType(final int index) { *

If you need to add another type to support a type S to use a LiveData in this plugin, * ensure the following is done on the Dart side: * - *

* In `../lib/src/live_data.dart`, add new cases for S in + *

* In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in * `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of type S from a * LiveData instance and in `LiveDataFlutterApiImpl#create` to create the expected type of * LiveData when requested. * *

On the native side, ensure the following is done: * - *

* Update `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for + *

* Make sure `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for * instances of type S. * Update `ObserverFlutterApiWrapper#onChanged` to properly handle * receiving calls with instances of type S if a LiveData instance is observed. */ @@ -146,6 +146,24 @@ private VideoResolutionFallbackRule(final int index) { } } + /** + * The types of capture request options this plugin currently supports. + * + *

If you need to add another option to support, ensure the following is done on the Dart side: + * + *

* In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this + * option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` to create the expected Map + * entry of option key index and value to send to the native side. + * + *

On the native side, ensure the following is done: + * + *

* Update `CaptureRequestOptionsHostApiImpl#create` to set the correct `CaptureRequest` key + * with a valid value type for this option. + * + *

See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest for the + * sorts of capture request options that can be supported via CameraX's interoperability with + * Camera2. + */ public enum CaptureRequestKeySupportedType { CONTROL_AE_LOCK(0); @@ -2489,6 +2507,7 @@ public interface ResolutionSelectorHostApi { void create( @NonNull Long identifier, @Nullable Long resolutionStrategyIdentifier, + @Nullable Long resolutionSelectorIdentifier, @Nullable Long aspectRatioStrategyIdentifier); /** The codec used by ResolutionSelectorHostApi. */ @@ -2512,13 +2531,17 @@ static void setup( ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); Number resolutionStrategyIdentifierArg = (Number) args.get(1); - Number aspectRatioStrategyIdentifierArg = (Number) args.get(2); + Number resolutionSelectorIdentifierArg = (Number) args.get(2); + Number aspectRatioStrategyIdentifierArg = (Number) args.get(3); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), (resolutionStrategyIdentifierArg == null) ? null : resolutionStrategyIdentifierArg.longValue(), + (resolutionSelectorIdentifierArg == null) + ? null + : resolutionSelectorIdentifierArg.longValue(), (aspectRatioStrategyIdentifierArg == null) ? null : aspectRatioStrategyIdentifierArg.longValue()); @@ -3778,7 +3801,10 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface FocusMeteringActionHostApi { - void create(@NonNull Long identifier, @NonNull List meteringPointInfos); + void create( + @NonNull Long identifier, + @NonNull List meteringPointInfos, + @Nullable Boolean disableAutoCancel); /** The codec used by FocusMeteringActionHostApi. */ static @NonNull MessageCodec getCodec() { @@ -3804,10 +3830,12 @@ static void setup( Number identifierArg = (Number) args.get(0); List meteringPointInfosArg = (List) args.get(1); + Boolean disableAutoCancelArg = (Boolean) args.get(2); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), - meteringPointInfosArg); + meteringPointInfosArg, + disableAutoCancelArg); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -3899,7 +3927,11 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) { public interface MeteringPointHostApi { void create( - @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size); + @NonNull Long identifier, + @NonNull Double x, + @NonNull Double y, + @Nullable Double size, + @NonNull Long cameraInfoId); @NonNull Double getDefaultPointSize(); @@ -3927,12 +3959,14 @@ static void setup( Double xArg = (Double) args.get(1); Double yArg = (Double) args.get(2); Double sizeArg = (Double) args.get(3); + Number cameraInfoIdArg = (Number) args.get(4); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), xArg, yArg, - sizeArg); + sizeArg, + (cameraInfoIdArg == null) ? null : cameraInfoIdArg.longValue()); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -4160,4 +4194,77 @@ public void error(Throwable error) { } } } + + private static class ResolutionFilterHostApiCodec extends StandardMessageCodec { + public static final ResolutionFilterHostApiCodec INSTANCE = new ResolutionFilterHostApiCodec(); + + private ResolutionFilterHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return ResolutionInfo.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof ResolutionInfo) { + stream.write(128); + writeValue(stream, ((ResolutionInfo) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface ResolutionFilterHostApi { + + void createWithOnePreferredSize( + @NonNull Long identifier, @NonNull ResolutionInfo preferredResolution); + + /** The codec used by ResolutionFilterHostApi. */ + static @NonNull MessageCodec getCodec() { + return ResolutionFilterHostApiCodec.INSTANCE; + } + /** + * Sets up an instance of `ResolutionFilterHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable ResolutionFilterHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ResolutionFilterHostApi.createWithOnePreferredSize", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + ResolutionInfo preferredResolutionArg = (ResolutionInfo) args.get(1); + try { + api.createWithOnePreferredSize( + (identifierArg == null) ? null : identifierArg.longValue(), + preferredResolutionArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java index 56ffcfb2e391..36306253ed13 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java @@ -4,13 +4,20 @@ package io.flutter.plugins.camerax; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.view.Display; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.camera.core.CameraInfo; +import androidx.camera.core.DisplayOrientedMeteringPointFactory; import androidx.camera.core.MeteringPoint; import androidx.camera.core.MeteringPointFactory; -import androidx.camera.core.SurfaceOrientedMeteringPointFactory; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointHostApi; +import java.util.Objects; /** * Host API implementation for {@link MeteringPoint}. @@ -25,17 +32,32 @@ public class MeteringPointHostApiImpl implements MeteringPointHostApi { /** Proxy for constructor and static methods of {@link MeteringPoint}. */ @VisibleForTesting public static class MeteringPointProxy { + Activity activity; /** * Creates a surface oriented {@link MeteringPoint} with the specified x, y, and size. * - *

A {@link SurfaceOrientedMeteringPointFactory} is used to construct the {@link - * MeteringPoint} because underlying the camera preview that this plugin uses is a Flutter - * texture that is backed by a {@link Surface} created by the Flutter Android embedding. + *

A {@link DisplayOrientedMeteringPointFactory} is used to construct the {@link + * MeteringPoint} because this factory handles the transformation of specified coordinates based + * on camera information and the device orientation automatically. */ @NonNull - public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Double size) { - SurfaceOrientedMeteringPointFactory factory = getSurfaceOrientedMeteringPointFactory(1f, 1f); + public MeteringPoint create( + @NonNull Double x, + @NonNull Double y, + @Nullable Double size, + @NonNull CameraInfo cameraInfo) { + Display display = null; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + display = activity.getDisplay(); + } else { + display = getDefaultDisplayForAndroidVersionBelowR(activity); + } + + DisplayOrientedMeteringPointFactory factory = + getDisplayOrientedMeteringPointFactory(display, cameraInfo, 1f, 1f); + if (size == null) { return factory.createPoint(x.floatValue(), y.floatValue()); } else { @@ -43,11 +65,18 @@ public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Doub } } + @NonNull + @SuppressWarnings("deprecation") + private Display getDefaultDisplayForAndroidVersionBelowR(@NonNull Activity activity) { + return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay(); + } + @VisibleForTesting @NonNull - public SurfaceOrientedMeteringPointFactory getSurfaceOrientedMeteringPointFactory( - float width, float height) { - return new SurfaceOrientedMeteringPointFactory(width, height); + public DisplayOrientedMeteringPointFactory getDisplayOrientedMeteringPointFactory( + @NonNull Display display, @NonNull CameraInfo cameraInfo, float width, float height) { + return new DisplayOrientedMeteringPointFactory(display, cameraInfo, width, height); } /** @@ -81,10 +110,23 @@ public MeteringPointHostApiImpl(@NonNull InstanceManager instanceManager) { this.proxy = proxy; } + public void setActivity(@NonNull Activity activity) { + this.proxy.activity = activity; + } + @Override public void create( - @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size) { - MeteringPoint meteringPoint = proxy.create(x, y, size); + @NonNull Long identifier, + @NonNull Double x, + @NonNull Double y, + @Nullable Double size, + @NonNull Long cameraInfoId) { + MeteringPoint meteringPoint = + proxy.create( + x, + y, + size, + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId))); instanceManager.addDartCreatedInstance(meteringPoint, identifier); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionFilterHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionFilterHostApiImpl.java new file mode 100644 index 000000000000..b2c3c9e69123 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionFilterHostApiImpl.java @@ -0,0 +1,94 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import android.util.Size; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.resolutionselector.ResolutionFilter; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionFilterHostApi; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; +import java.util.List; + +/** + * Host API implementation for {@link ResolutionFilter}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class ResolutionFilterHostApiImpl implements ResolutionFilterHostApi { + private final InstanceManager instanceManager; + private final ResolutionFilterFactory resolutionFilterFactory; + + /** + * Proxy for constructing {@link ResolutionFilter}s with particular attributes, as detailed by + * documentation below. + */ + @VisibleForTesting + public static class ResolutionFilterFactory { + /** + * Creates an instance of {@link ResolutionFilter} that moves the {@code preferredSize} to the + * front of the list of supported resolutions so that it can be prioritized by CameraX. + * + *

If the preferred {@code Size} is not found, then this creates a {@link ResolutionFilter} + * that leaves the priority of supported resolutions unadjusted. + */ + @NonNull + public ResolutionFilter createWithOnePreferredSize(@NonNull Size preferredSize) { + return new ResolutionFilter() { + @Override + @NonNull + public List filter(@NonNull List supportedSizes, int rotationDegrees) { + int preferredSizeIndex = supportedSizes.indexOf(preferredSize); + + if (preferredSizeIndex > -1) { + supportedSizes.remove(preferredSizeIndex); + supportedSizes.add(0, preferredSize); + } + + return supportedSizes; + } + }; + } + } + + /** + * Constructs a {@link ResolutionFilterHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public ResolutionFilterHostApiImpl(@NonNull InstanceManager instanceManager) { + this(instanceManager, new ResolutionFilterFactory()); + } + + /** + * Constructs a {@link ResolutionFilterHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param resolutionFilterFactory proxy for constructing different kinds of {@link + * ResolutionFilter}s + */ + @VisibleForTesting + ResolutionFilterHostApiImpl( + @NonNull InstanceManager instanceManager, + @NonNull ResolutionFilterFactory resolutionFilterFactory) { + this.instanceManager = instanceManager; + this.resolutionFilterFactory = resolutionFilterFactory; + } + + /** + * Creates a {@link ResolutionFilter} that prioritizes the specified {@code preferredResolution} + * over all other resolutions. + */ + @Override + public void createWithOnePreferredSize( + @NonNull Long identifier, @NonNull ResolutionInfo preferredResolution) { + Size preferredSize = + new Size( + preferredResolution.getWidth().intValue(), preferredResolution.getHeight().intValue()); + instanceManager.addDartCreatedInstance( + resolutionFilterFactory.createWithOnePreferredSize(preferredSize), identifier); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java index 4aa11cd593f2..0a5fe750d163 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.camera.core.resolutionselector.AspectRatioStrategy; +import androidx.camera.core.resolutionselector.ResolutionFilter; import androidx.camera.core.resolutionselector.ResolutionSelector; import androidx.camera.core.resolutionselector.ResolutionStrategy; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionSelectorHostApi; @@ -30,7 +31,8 @@ public static class ResolutionSelectorProxy { @NonNull public ResolutionSelector create( @Nullable ResolutionStrategy resolutionStrategy, - @Nullable AspectRatioStrategy aspectRatioStrategy) { + @Nullable AspectRatioStrategy aspectRatioStrategy, + @Nullable ResolutionFilter resolutionFilter) { final ResolutionSelector.Builder builder = new ResolutionSelector.Builder(); if (resolutionStrategy != null) { builder.setResolutionStrategy(resolutionStrategy); @@ -38,6 +40,9 @@ public ResolutionSelector create( if (aspectRatioStrategy != null) { builder.setAspectRatioStrategy(aspectRatioStrategy); } + if (resolutionFilter != null) { + builder.setResolutionFilter(resolutionFilter); + } return builder.build(); } } @@ -65,13 +70,14 @@ public ResolutionSelectorHostApiImpl(@NonNull InstanceManager instanceManager) { } /** - * Creates a {@link ResolutionSelector} instance with the {@link ResolutionStrategy} and {@link - * AspectRatio} that have the identifiers specified if provided. + * Creates a {@link ResolutionSelector} instance with the {@link ResolutionStrategy}, {@link + * ResolutionFilter}, and {@link AspectRatio} that have the identifiers specified if provided. */ @Override public void create( @NonNull Long identifier, @Nullable Long resolutionStrategyIdentifier, + @Nullable Long resolutionFilterIdentifier, @Nullable Long aspectRatioStrategyIdentifier) { instanceManager.addDartCreatedInstance( proxy.create( @@ -81,7 +87,10 @@ public void create( aspectRatioStrategyIdentifier == null ? null : Objects.requireNonNull( - instanceManager.getInstance(aspectRatioStrategyIdentifier))), + instanceManager.getInstance(aspectRatioStrategyIdentifier)), + resolutionFilterIdentifier == null + ? null + : Objects.requireNonNull(instanceManager.getInstance(resolutionFilterIdentifier))), identifier); } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java index 8c9daf8e0143..fcca0f8eb0bc 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java @@ -92,6 +92,8 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { mock(SystemServicesHostApiImpl.class); final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class); + final MeteringPointHostApiImpl mockMeteringPointHostApiImpl = + mock(MeteringPointHostApiImpl.class); final ArgumentCaptor permissionsRegistryCaptor = ArgumentCaptor.forClass(PermissionsRegistry.class); @@ -103,6 +105,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl; plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl; + plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onAttachedToActivity(activityPluginBinding); @@ -110,6 +113,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { // Check Activity references are set. verify(mockSystemServicesHostApiImpl).setActivity(mockActivity); verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity); + verify(mockMeteringPointHostApiImpl).setActivity(mockActivity); // Check permissions registry reference is set. verify(mockSystemServicesHostApiImpl) @@ -129,11 +133,14 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { mock(SystemServicesHostApiImpl.class); final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class); + final MeteringPointHostApiImpl mockMeteringPointHostApiImpl = + mock(MeteringPointHostApiImpl.class); plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl; plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl; plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl; plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl; + plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivityForConfigChanges(); @@ -142,6 +149,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { verify(mockLiveDataHostApiImpl).setLifecycleOwner(null); verify(mockSystemServicesHostApiImpl).setActivity(null); verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null); + verify(mockMeteringPointHostApiImpl).setActivity(null); } @Test @@ -161,6 +169,8 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { mock(ImageAnalysisHostApiImpl.class); final CameraControlHostApiImpl mockCameraControlHostApiImpl = mock(CameraControlHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); when(flutterPluginBinding.getApplicationContext()).thenReturn(mockContext); @@ -172,6 +182,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl; plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivityForConfigChanges(); @@ -183,6 +194,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { verify(mockImageCaptureHostApiImpl).setContext(mockContext); verify(mockImageAnalysisHostApiImpl).setContext(mockContext); verify(mockCameraControlHostApiImpl).setContext(mockContext); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext); } @Test @@ -251,6 +263,10 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg mock(CameraControlHostApiImpl.class); final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); + final MeteringPointHostApiImpl mockMeteringPointHostApiImpl = + mock(MeteringPointHostApiImpl.class); final ArgumentCaptor permissionsRegistryCaptor = ArgumentCaptor.forClass(PermissionsRegistry.class); @@ -265,7 +281,9 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl; plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl; + plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onReattachedToActivityForConfigChanges(activityPluginBinding); @@ -273,6 +291,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg // Check Activity references are set. verify(mockSystemServicesHostApiImpl).setActivity(mockActivity); verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity); + verify(mockMeteringPointHostApiImpl).setActivity(mockActivity); // Check Activity as Context references are set. verify(mockProcessCameraProviderHostApiImpl).setContext(mockActivity); @@ -282,6 +301,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg verify(mockImageCaptureHostApiImpl).setContext(mockActivity); verify(mockImageAnalysisHostApiImpl).setContext(mockActivity); verify(mockCameraControlHostApiImpl).setContext(mockActivity); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockActivity); // Check permissions registry reference is set. verify(mockSystemServicesHostApiImpl) @@ -300,11 +320,14 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc final LiveDataHostApiImpl mockLiveDataHostApiImpl = mock(LiveDataHostApiImpl.class); final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class); + final MeteringPointHostApiImpl mockMeteringPointHostApiImpl = + mock(MeteringPointHostApiImpl.class); plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl; plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl; plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl; plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl; + plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivityForConfigChanges(); @@ -313,6 +336,7 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc verify(mockLiveDataHostApiImpl).setLifecycleOwner(null); verify(mockSystemServicesHostApiImpl).setActivity(null); verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null); + verify(mockMeteringPointHostApiImpl).setActivity(null); } @Test @@ -331,6 +355,8 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind final ImageCaptureHostApiImpl mockImageCaptureHostApiImpl = mock(ImageCaptureHostApiImpl.class); final CameraControlHostApiImpl mockCameraControlHostApiImpl = mock(CameraControlHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); final ArgumentCaptor permissionsRegistryCaptor = ArgumentCaptor.forClass(PermissionsRegistry.class); @@ -344,6 +370,7 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl; plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivity(); @@ -355,5 +382,6 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind verify(mockImageCaptureHostApiImpl).setContext(mockContext); verify(mockImageAnalysisHostApiImpl).setContext(mockContext); verify(mockCameraControlHostApiImpl).setContext(mockContext); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext); } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java index 42ace7a51fb5..a0fe8f977ee0 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java @@ -149,6 +149,22 @@ public void setZoomRatio_setsZoomAsExpected() { failedSetZoomRatioCallback.onFailure(testThrowable); verify(failedMockResult).error(testThrowable); + + // Test response to canceled operation. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result canceledOpResult = + mock(GeneratedCameraXLibrary.Result.class); + final CameraControl.OperationCanceledException canceledOpThrowable = + mock(CameraControl.OperationCanceledException.class); + cameraControlHostApiImpl.setZoomRatio(cameraControlIdentifier, zoomRatio, canceledOpResult); + mockedFutures.verify( + () -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback canceledOpCallback = futureCallbackCaptor.getValue(); + + canceledOpCallback.onFailure(canceledOpThrowable); + verify(canceledOpResult).success(null); } } @@ -212,6 +228,25 @@ public void startFocusAndMetering_startsFocusAndMeteringAsExpected() { failedCallback.onFailure(testThrowable); verify(failedMockResult).error(testThrowable); + + // Test response to canceled operation. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result canceledOpResult = + mock(GeneratedCameraXLibrary.Result.class); + final CameraControl.OperationCanceledException canceledOpThrowable = + mock(CameraControl.OperationCanceledException.class); + cameraControlHostApiImpl.startFocusAndMetering( + cameraControlIdentifier, mockActionId, canceledOpResult); + mockedFutures.verify( + () -> + Futures.addCallback( + eq(startFocusAndMeteringFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback canceledOpCallback = futureCallbackCaptor.getValue(); + + canceledOpCallback.onFailure(canceledOpThrowable); + verify(canceledOpResult).success(null); } } @@ -326,6 +361,25 @@ public void setExposureCompensationIndex_setsExposureCompensationIndexAsExpected failedCallback.onFailure(testThrowable); verify(failedMockResult).error(testThrowable); + + // Test response to canceled operation. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result canceledOpResult = + mock(GeneratedCameraXLibrary.Result.class); + final CameraControl.OperationCanceledException canceledOpThrowable = + mock(CameraControl.OperationCanceledException.class); + cameraControlHostApiImpl.setExposureCompensationIndex( + cameraControlIdentifier, index, canceledOpResult); + mockedFutures.verify( + () -> + Futures.addCallback( + eq(setExposureCompensationIndexFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback canceledOpCallback = futureCallbackCaptor.getValue(); + + canceledOpCallback.onFailure(canceledOpThrowable); + verify(canceledOpResult).success(null); } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java index 64db80541619..6d96d3036cd7 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,7 +44,7 @@ public void tearDown() { } @Test - public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatHasMode() { + public void hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatHasMode() { FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); FocusMeteringActionHostApiImpl hostApi = @@ -89,7 +90,7 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3); - hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos); + hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3); @@ -98,7 +99,8 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH } @Test - public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() { + public void + hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() { FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); FocusMeteringActionHostApiImpl hostApi = @@ -142,11 +144,49 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatD List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3); - hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos); + hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3); assertEquals( testInstanceManager.getInstance(focusMeteringActionIdentifier), focusMeteringAction); } + + @Test + public void hostApiCreate_disablesAutoCancelAsExpected() { + FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = + spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); + FocusMeteringActionHostApiImpl hostApi = + new FocusMeteringActionHostApiImpl(testInstanceManager, proxySpy); + + FocusMeteringAction.Builder mockFocusMeteringActionBuilder = + mock(FocusMeteringAction.Builder.class); + final MeteringPoint mockMeteringPoint = mock(MeteringPoint.class); + final Long mockMeteringPointId = 47L; + + MeteringPointInfo fakeMeteringPointInfo = + new MeteringPointInfo.Builder() + .setMeteringPointId(mockMeteringPointId) + .setMeteringMode(null) + .build(); + + testInstanceManager.addDartCreatedInstance(mockMeteringPoint, mockMeteringPointId); + + when(proxySpy.getFocusMeteringActionBuilder(mockMeteringPoint)) + .thenReturn(mockFocusMeteringActionBuilder); + when(mockFocusMeteringActionBuilder.build()).thenReturn(focusMeteringAction); + + List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo); + + // Test not disabling auto cancel. + hostApi.create(73L, mockMeteringPointInfos, /* disableAutoCancel */ null); + verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel(); + + hostApi.create(74L, mockMeteringPointInfos, /* disableAutoCancel */ false); + verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel(); + + // Test disabling auto cancel. + hostApi.create(75L, mockMeteringPointInfos, /* disableAutoCancel */ true); + verify(mockFocusMeteringActionBuilder).disableAutoCancel(); + } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java index f245eb53124e..0734f6ba6a9d 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java @@ -10,21 +10,30 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; +import android.content.Context; +import android.view.Display; +import android.view.WindowManager; +import androidx.camera.core.CameraInfo; +import androidx.camera.core.DisplayOrientedMeteringPointFactory; import androidx.camera.core.MeteringPoint; import androidx.camera.core.MeteringPointFactory; -import androidx.camera.core.SurfaceOrientedMeteringPointFactory; import io.flutter.plugin.common.BinaryMessenger; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +@RunWith(RobolectricTestRunner.class) public class MeteringPointTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @@ -44,49 +53,158 @@ public void tearDown() { } @Test - public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified() { - MeteringPointHostApiImpl.MeteringPointProxy proxySpy = + @Config(sdk = 30) + public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified_AboveAndroid30() { + final MeteringPointHostApiImpl.MeteringPointProxy proxySpy = spy(new MeteringPointHostApiImpl.MeteringPointProxy()); - MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy); + final MeteringPointHostApiImpl hostApi = + new MeteringPointHostApiImpl(testInstanceManager, proxySpy); final Long meteringPointIdentifier = 78L; - final Float x = 0.3f; - final Float y = 0.2f; - final Float size = 6f; + final Float x = 0.25f; + final Float y = 0.18f; + final Float size = 0.6f; final Float surfaceWidth = 1f; final Float surfaceHeight = 1f; - SurfaceOrientedMeteringPointFactory mockSurfaceOrientedMeteringPointFactory = - mock(SurfaceOrientedMeteringPointFactory.class); - - when(proxySpy.getSurfaceOrientedMeteringPointFactory(surfaceWidth, surfaceHeight)) - .thenReturn(mockSurfaceOrientedMeteringPointFactory); - when(mockSurfaceOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint); - - hostApi.create(meteringPointIdentifier, x.doubleValue(), y.doubleValue(), size.doubleValue()); - - verify(mockSurfaceOrientedMeteringPointFactory).createPoint(x, y, size); + final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory = + mock(DisplayOrientedMeteringPointFactory.class); + final Activity mockActivity = mock(Activity.class); + final Display mockDisplay = mock(Display.class); + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long mockCameraInfoId = 55L; + + hostApi.setActivity(mockActivity); + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId); + + when(mockActivity.getDisplay()).thenReturn(mockDisplay); + when(proxySpy.getDisplayOrientedMeteringPointFactory( + mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight)) + .thenReturn(mockDisplayOrientedMeteringPointFactory); + when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint); + + hostApi.create( + meteringPointIdentifier, + x.doubleValue(), + y.doubleValue(), + size.doubleValue(), + mockCameraInfoId); + + verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y, size); assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint); } @Test - public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified() { - MeteringPointHostApiImpl.MeteringPointProxy proxySpy = + @Config(sdk = 29) + @SuppressWarnings("deprecation") + public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified_BelowAndroid30() { + final MeteringPointHostApiImpl.MeteringPointProxy proxySpy = spy(new MeteringPointHostApiImpl.MeteringPointProxy()); - MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy); + final MeteringPointHostApiImpl hostApi = + new MeteringPointHostApiImpl(testInstanceManager, proxySpy); final Long meteringPointIdentifier = 78L; final Float x = 0.3f; final Float y = 0.2f; + final Float size = 6f; final Float surfaceWidth = 1f; final Float surfaceHeight = 1f; - SurfaceOrientedMeteringPointFactory mockSurfaceOrientedMeteringPointFactory = - mock(SurfaceOrientedMeteringPointFactory.class); - - when(proxySpy.getSurfaceOrientedMeteringPointFactory(surfaceWidth, surfaceHeight)) - .thenReturn(mockSurfaceOrientedMeteringPointFactory); - when(mockSurfaceOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint); + final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory = + mock(DisplayOrientedMeteringPointFactory.class); + final Activity mockActivity = mock(Activity.class); + final WindowManager mockWindowManager = mock(WindowManager.class); + final Display mockDisplay = mock(Display.class); + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long mockCameraInfoId = 5L; + + hostApi.setActivity(mockActivity); + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId); + + when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager); + when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay); + when(proxySpy.getDisplayOrientedMeteringPointFactory( + mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight)) + .thenReturn(mockDisplayOrientedMeteringPointFactory); + when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint); + + hostApi.create( + meteringPointIdentifier, + x.doubleValue(), + y.doubleValue(), + size.doubleValue(), + mockCameraInfoId); + + verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y, size); + assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint); + } - hostApi.create(meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null); + @Test + @Config(sdk = 30) + public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified_AboveAndroid30() { + final MeteringPointHostApiImpl.MeteringPointProxy proxySpy = + spy(new MeteringPointHostApiImpl.MeteringPointProxy()); + final MeteringPointHostApiImpl hostApi = + new MeteringPointHostApiImpl(testInstanceManager, proxySpy); + final Long meteringPointIdentifier = 78L; + final Float x = 0.23f; + final Float y = 0.32f; + final Float surfaceWidth = 1f; + final Float surfaceHeight = 1f; + final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory = + mock(DisplayOrientedMeteringPointFactory.class); + final Activity mockActivity = mock(Activity.class); + final Display mockDisplay = mock(Display.class); + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long mockCameraInfoId = 6L; + + hostApi.setActivity(mockActivity); + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId); + + when(mockActivity.getDisplay()).thenReturn(mockDisplay); + when(proxySpy.getDisplayOrientedMeteringPointFactory( + mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight)) + .thenReturn(mockDisplayOrientedMeteringPointFactory); + when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint); + + hostApi.create( + meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null, mockCameraInfoId); + + verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y); + assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint); + } - verify(mockSurfaceOrientedMeteringPointFactory).createPoint(x, y); + @Test + @Config(sdk = 29) + @SuppressWarnings("deprecation") + public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified_BelowAndroid30() { + final MeteringPointHostApiImpl.MeteringPointProxy proxySpy = + spy(new MeteringPointHostApiImpl.MeteringPointProxy()); + final MeteringPointHostApiImpl hostApi = + new MeteringPointHostApiImpl(testInstanceManager, proxySpy); + final Long meteringPointIdentifier = 78L; + final Float x = 0.1f; + final Float y = 0.8f; + final Float surfaceWidth = 1f; + final Float surfaceHeight = 1f; + final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory = + mock(DisplayOrientedMeteringPointFactory.class); + final Activity mockActivity = mock(Activity.class); + final WindowManager mockWindowManager = mock(WindowManager.class); + final Display mockDisplay = mock(Display.class); + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long mockCameraInfoId = 7L; + + hostApi.setActivity(mockActivity); + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId); + + when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager); + when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay); + when(proxySpy.getDisplayOrientedMeteringPointFactory( + mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight)) + .thenReturn(mockDisplayOrientedMeteringPointFactory); + when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint); + + hostApi.create( + meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null, mockCameraInfoId); + + verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y); assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionFilterTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionFilterTest.java new file mode 100644 index 000000000000..150f5676739a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionFilterTest.java @@ -0,0 +1,76 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; + +import android.util.Size; +import androidx.camera.core.resolutionselector.ResolutionFilter; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ResolutionFilterTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreateWithOnePreferredSize_createsExpectedResolutionFilterInstance() { + final ResolutionFilterHostApiImpl hostApi = new ResolutionFilterHostApiImpl(instanceManager); + final long instanceIdentifier = 50; + final long preferredResolutionWidth = 20; + final long preferredResolutionHeight = 80; + final ResolutionInfo preferredResolution = + new ResolutionInfo.Builder() + .setWidth(preferredResolutionWidth) + .setHeight(preferredResolutionHeight) + .build(); + + hostApi.createWithOnePreferredSize(instanceIdentifier, preferredResolution); + + // Test that instance filters supported resolutions as expected. + final ResolutionFilter resolutionFilter = instanceManager.getInstance(instanceIdentifier); + final Size fakeSupportedSize1 = new Size(720, 480); + final Size fakeSupportedSize2 = new Size(20, 80); + final Size fakeSupportedSize3 = new Size(2, 8); + final Size preferredSize = + new Size((int) preferredResolutionWidth, (int) preferredResolutionHeight); + + final ArrayList fakeSupportedSizes = new ArrayList(); + fakeSupportedSizes.add(fakeSupportedSize1); + fakeSupportedSizes.add(fakeSupportedSize2); + fakeSupportedSizes.add(preferredSize); + fakeSupportedSizes.add(fakeSupportedSize3); + + // Test the case where preferred resolution is supported. + List filteredSizes = resolutionFilter.filter(fakeSupportedSizes, 90); + assertEquals(filteredSizes.get(0), preferredSize); + + // Test the case where preferred resolution is not supported. + fakeSupportedSizes.remove(0); + filteredSizes = resolutionFilter.filter(fakeSupportedSizes, 90); + assertEquals(filteredSizes, fakeSupportedSizes); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java index f323e4706c9b..0f45f07b4f7a 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import androidx.camera.core.resolutionselector.AspectRatioStrategy; +import androidx.camera.core.resolutionselector.ResolutionFilter; import androidx.camera.core.resolutionselector.ResolutionSelector; import androidx.camera.core.resolutionselector.ResolutionStrategy; import org.junit.After; @@ -46,13 +47,21 @@ public void hostApiCreate_createsExpectedResolutionSelectorInstance() { final long aspectRatioStrategyIdentifier = 15; instanceManager.addDartCreatedInstance(mockAspectRatioStrategy, aspectRatioStrategyIdentifier); - when(mockProxy.create(mockResolutionStrategy, mockAspectRatioStrategy)) + final ResolutionFilter mockResolutionFilter = mock(ResolutionFilter.class); + final long resolutionFilterIdentifier = 33; + instanceManager.addDartCreatedInstance(mockResolutionFilter, resolutionFilterIdentifier); + + when(mockProxy.create(mockResolutionStrategy, mockAspectRatioStrategy, mockResolutionFilter)) .thenReturn(mockResolutionSelector); final ResolutionSelectorHostApiImpl hostApi = new ResolutionSelectorHostApiImpl(instanceManager, mockProxy); final long instanceIdentifier = 0; - hostApi.create(instanceIdentifier, resolutionStrategyIdentifier, aspectRatioStrategyIdentifier); + hostApi.create( + instanceIdentifier, + resolutionStrategyIdentifier, + resolutionFilterIdentifier, + aspectRatioStrategyIdentifier); assertEquals(instanceManager.getInstance(instanceIdentifier), mockResolutionSelector); } diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart index 4484c5dac982..22221fd1de96 100644 --- a/packages/camera/camera_android_camerax/example/lib/main.dart +++ b/packages/camera/camera_android_camerax/example/lib/main.dart @@ -282,14 +282,15 @@ class _CameraExampleHomeState extends State IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? onExposureModeButtonPressed + : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: - () {}, // TODO(camsim99): Add functionality back here. + controller != null ? onFocusModeButtonPressed : null, ) ] : [], @@ -392,22 +393,32 @@ class _CameraExampleHomeState extends State children: [ TextButton( style: styleAuto, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. - onLongPress: - () {}, // TODO(camsim99): Add functionality back here., + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setExposurePoint(controller!.cameraId, null); + showInSnackBar('Resetting exposure point'); + } + }, child: const Text('AUTO'), ), TextButton( style: styleLocked, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.locked) + : null, child: const Text('LOCKED'), ), TextButton( style: styleLocked, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => controller!.setExposureOffset(0.0) + : null, child: const Text('RESET OFFSET'), ), ], @@ -466,16 +477,23 @@ class _CameraExampleHomeState extends State children: [ TextButton( style: styleAuto, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. - onLongPress: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setFocusPoint(controller!.cameraId, null); + } + showInSnackBar('Resetting focus point'); + }, child: const Text('AUTO'), ), TextButton( style: styleLocked, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, child: const Text('LOCKED'), ), ], diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index b05788d68a43..19aaea83fa3d 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -3,28 +3,37 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math' show Point; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/services.dart' show DeviceOrientation; -import 'package:flutter/widgets.dart'; +import 'package:flutter/services.dart' + show DeviceOrientation, PlatformException; +import 'package:flutter/widgets.dart' + show Size, Texture, Widget, visibleForTesting; import 'package:stream_transform/stream_transform.dart'; import 'analyzer.dart'; +import 'aspect_ratio_strategy.dart'; import 'camera.dart'; +import 'camera2_camera_control.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; import 'camerax_proxy.dart'; +import 'capture_request_options.dart'; import 'device_orientation_manager.dart'; import 'exposure_state.dart'; import 'fallback_strategy.dart'; +import 'focus_metering_action.dart'; +import 'focus_metering_result.dart'; import 'image_analysis.dart'; import 'image_capture.dart'; import 'image_proxy.dart'; import 'live_data.dart'; +import 'metering_point.dart'; import 'observer.dart'; import 'pending_recording.dart'; import 'plane_proxy.dart'; @@ -33,6 +42,7 @@ import 'process_camera_provider.dart'; import 'quality_selector.dart'; import 'recorder.dart'; import 'recording.dart'; +import 'resolution_filter.dart'; import 'resolution_selector.dart'; import 'resolution_strategy.dart'; import 'surface.dart'; @@ -69,6 +79,9 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting CameraInfo? cameraInfo; + /// The [CameraControl] instance that corresponds to the [camera] instance. + late CameraControl cameraControl; + /// The [LiveData] of the [CameraState] that represents the state of the /// [camera] instance. LiveData? liveCameraState; @@ -180,6 +193,38 @@ class AndroidCameraCameraX extends CameraPlatform { /// for an example on how setting target rotations for [UseCase]s works. bool shouldSetDefaultRotation = false; + /// Error code indicating that an exposure offset value failed to be set. + static const String setExposureOffsetFailedErrorCode = + 'setExposureOffsetFailed'; + + /// The currently set [FocusMeteringAction] used to enable auto-focus and + /// auto-exposure. + @visibleForTesting + FocusMeteringAction? currentFocusMeteringAction; + + /// Current focus mode set via [setFocusMode]. + /// + /// CameraX defaults to auto focus mode. + FocusMode _currentFocusMode = FocusMode.auto; + + /// Current exposure mode set via [setExposureMode]. + /// + /// CameraX defaults to auto exposure mode. + ExposureMode _currentExposureMode = ExposureMode.auto; + + /// Whether or not a default focus point of the entire sensor area was focused + /// and locked. + /// + /// This should only be true if [setExposureMode] was called to set + /// [FocusMode.locked] and no previous focus point was set via + /// [setFocusPoint]. + bool _defaultFocusPointLocked = false; + + /// Error code indicating that exposure compensation is not supported by + /// CameraX for the device. + static const String exposureCompensationNotSupported = + 'exposureCompensationNotSupported'; + /// Returns list of all available cameras and their descriptions. @override Future> availableCameras() async { @@ -330,16 +375,13 @@ class AndroidCameraCameraX extends CameraPlatform { final ResolutionInfo previewResolutionInfo = await preview!.getResolutionInfo(); - // Retrieve exposure and focus mode configurations: - // TODO(camsim99): Implement support for retrieving exposure mode configuration. - // https://github.com/flutter/flutter/issues/120468 + // Mark auto-focus, auto-exposure and setting points for focus & exposure + // as available operations as CameraX does its best across devices to + // support these by default. const ExposureMode exposureMode = ExposureMode.auto; - const bool exposurePointSupported = false; - - // TODO(camsim99): Implement support for retrieving focus mode configuration. - // https://github.com/flutter/flutter/issues/120467 const FocusMode focusMode = FocusMode.auto; - const bool focusPointSupported = false; + const bool exposurePointSupported = true; + const bool focusPointSupported = true; cameraEventStreamController.add(CameraInitializedEvent( cameraId, @@ -430,6 +472,28 @@ class AndroidCameraCameraX extends CameraPlatform { captureOrientationLocked = false; } + /// Sets the exposure point for automatically determining the exposure values. + /// + /// Supplying `null` for the [point] argument will result in resetting to the + /// original exposure point value. + /// + /// Supplied non-null point must be mapped to the entire un-altered preview + /// surface for the exposure point to be applied accurately. + /// + /// [cameraId] is not used. + @override + Future setExposurePoint(int cameraId, Point? point) async { + // We lock the new focus and metering action if focus mode has been locked + // to ensure that the current focus point remains locked. Any exposure mode + // setting will not be impacted by this lock (setting an exposure mode + // is implemented with Camera2 interop that will override settings to + // achieve the expected exposure mode as needed). + await _startFocusAndMeteringForPoint( + point: point, + meteringMode: FocusMeteringAction.flagAe, + disableAutoCancel: _currentFocusMode == FocusMode.locked); + } + /// Gets the minimum supported exposure offset for the selected camera in EV units. /// /// [cameraId] not used. @@ -450,15 +514,190 @@ class AndroidCameraCameraX extends CameraPlatform { exposureState.exposureCompensationStep; } + /// Sets the focus mode for taking pictures. + /// + /// Setting [FocusMode.locked] will lock the current focus point if one exists + /// or the center of entire sensor area if not, and will stay locked until + /// either: + /// * Another focus point is set via [setFocusPoint] (which will then become + /// the locked focus point), or + /// * Locked focus mode is unset by setting [FocusMode.auto]. + @override + Future setFocusMode(int cameraId, FocusMode mode) async { + if (_currentFocusMode == mode) { + // Desired focus mode is already set. + return; + } + + MeteringPoint? autoFocusPoint; + bool? disableAutoCancel; + switch (mode) { + case FocusMode.auto: + // Determine auto-focus point to restore, if any. We do not restore + // default auto-focus point if set previously to lock focus. + final MeteringPoint? unLockedFocusPoint = _defaultFocusPointLocked + ? null + : currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList() + .first + .$1; + _defaultFocusPointLocked = false; + autoFocusPoint = unLockedFocusPoint; + disableAutoCancel = false; + case FocusMode.locked: + MeteringPoint? lockedFocusPoint; + + // Determine if there is an auto-focus point set currently to lock. + if (currentFocusMeteringAction != null) { + final List<(MeteringPoint, int?)> possibleCurrentAfPoints = + currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + lockedFocusPoint = possibleCurrentAfPoints.isEmpty + ? null + : possibleCurrentAfPoints.first.$1; + } + + // If there isn't, lock center of entire sensor area by default. + if (lockedFocusPoint == null) { + lockedFocusPoint = + proxy.createMeteringPoint(0.5, 0.5, 1, cameraInfo!); + _defaultFocusPointLocked = true; + } + + autoFocusPoint = lockedFocusPoint; + disableAutoCancel = true; + } + // Start appropriate focus and metering action. + final bool focusAndMeteringWasSuccessful = await _startFocusAndMeteringFor( + meteringPoint: autoFocusPoint, + meteringMode: FocusMeteringAction.flagAf, + disableAutoCancel: disableAutoCancel); + + if (!focusAndMeteringWasSuccessful) { + // Do not update current focus mode. + return; + } + + // Update current focus mode. + _currentFocusMode = mode; + + // If focus mode was just locked and exposure mode is not, set auto exposure + // mode to ensure that disabling auto-cancel does not interfere with + // automatic exposure metering. + if (_currentExposureMode == ExposureMode.auto && + _currentFocusMode == FocusMode.locked) { + await setExposureMode(cameraId, _currentExposureMode); + } + } + /// Gets the supported step size for exposure offset for the selected camera in EV units. /// - /// Returns 0 when exposure compensation is not supported. + /// Returns -1 if exposure compensation is not supported for the device. /// /// [cameraId] not used. @override Future getExposureOffsetStepSize(int cameraId) async { final ExposureState exposureState = await cameraInfo!.getExposureState(); - return exposureState.exposureCompensationStep; + final double exposureOffsetStepSize = + exposureState.exposureCompensationStep; + if (exposureOffsetStepSize == 0) { + // CameraX returns a step size of 0 if exposure compensation is not + // supported for the device. + return -1; + } + return exposureOffsetStepSize; + } + + /// Sets the exposure offset for the selected camera. + /// + /// The supplied [offset] value should be in EV units. 1 EV unit represents a + /// doubling in brightness. It should be between the minimum and maximum offsets + /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. + /// Throws a `CameraException` when trying to set exposure offset on a device + /// that doesn't support exposure compensationan or if setting the offset fails, + /// like in the case that an illegal offset is supplied. + /// + /// When the supplied [offset] value does not align with the step size obtained + /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. + /// + /// Returns the (rounded) offset value that was set. + @override + Future setExposureOffset(int cameraId, double offset) async { + final double exposureOffsetStepSize = + (await cameraInfo!.getExposureState()).exposureCompensationStep; + if (exposureOffsetStepSize == 0) { + throw CameraException(exposureCompensationNotSupported, + 'Exposure compensation not supported'); + } + + // (Exposure compensation index) * (exposure offset step size) = + // (exposure offset). + final int roundedExposureCompensationIndex = + (offset / exposureOffsetStepSize).round(); + + try { + final int? newIndex = await cameraControl + .setExposureCompensationIndex(roundedExposureCompensationIndex); + if (newIndex == null) { + throw CameraException(setExposureOffsetFailedErrorCode, + 'Setting exposure compensation index was canceled due to the camera being closed or a new request being submitted.'); + } + + return newIndex.toDouble(); + } on PlatformException catch (e) { + throw CameraException( + setExposureOffsetFailedErrorCode, + e.message ?? + 'Setting the camera exposure compensation index failed.'); + } + } + + /// Sets the focus point for automatically determining the focus values. + /// + /// Supplying `null` for the [point] argument will result in resetting to the + /// original focus point value. + /// + /// Supplied non-null point must be mapped to the entire un-altered preview + /// surface for the focus point to be applied accurately. + /// + /// [cameraId] is not used. + @override + Future setFocusPoint(int cameraId, Point? point) async { + // We lock the new focus and metering action if focus mode has been locked + // to ensure that the current focus point remains locked. Any exposure mode + // setting will not be impacted by this lock (setting an exposure mode + // is implemented with Camera2 interop that will override settings to + // achieve the expected exposure mode as needed). + await _startFocusAndMeteringForPoint( + point: point, + meteringMode: FocusMeteringAction.flagAf, + disableAutoCancel: _currentFocusMode == FocusMode.locked); + } + + /// Sets the exposure mode for taking pictures. + /// + /// Setting [ExposureMode.locked] will lock current exposure point until it + /// is unset by setting [ExposureMode.auto]. + /// + /// [cameraId] is not used. + @override + Future setExposureMode(int cameraId, ExposureMode mode) async { + final Camera2CameraControl camera2Control = + proxy.getCamera2CameraControl(cameraControl); + final bool lockExposureMode = mode == ExposureMode.locked; + + final CaptureRequestOptions captureRequestOptions = proxy + .createCaptureRequestOptions(<( + CaptureRequestKeySupportedType, + Object? + )>[(CaptureRequestKeySupportedType.controlAeLock, lockExposureMode)]); + + await camera2Control.addCaptureRequestOptions(captureRequestOptions); + _currentExposureMode = mode; } /// Gets the maximum supported zoom level for the selected camera. @@ -502,7 +741,6 @@ class AndroidCameraCameraX extends CameraPlatform { /// Throws a `CameraException` when an illegal zoom level is supplied. @override Future setZoomLevel(int cameraId, double zoom) async { - final CameraControl cameraControl = await camera!.getCameraControl(); await cameraControl.setZoomRatio(zoom); } @@ -587,10 +825,8 @@ class AndroidCameraCameraX extends CameraPlatform { /// respectively. @override Future setFlashMode(int cameraId, FlashMode mode) async { - CameraControl? cameraControl; // Turn off torch mode if it is enabled and not being redundantly set. if (mode != FlashMode.torch && torchEnabled) { - cameraControl = await camera!.getCameraControl(); await cameraControl.enableTorch(false); torchEnabled = false; } @@ -608,7 +844,6 @@ class AndroidCameraCameraX extends CameraPlatform { // Torch mode enabled already. return; } - cameraControl = await camera!.getCameraControl(); await cameraControl.enableTorch(true); torchEnabled = true; } @@ -833,14 +1068,15 @@ class AndroidCameraCameraX extends CameraPlatform { // Methods concerning camera state: - /// Updates [cameraInfo] to the information corresponding to [camera] and - /// adds observers to the [LiveData] of the [CameraState] of the current - /// [camera], saved as [liveCameraState]. + /// Updates [cameraInfo] and [cameraControl] to the information corresponding + /// to [camera] and adds observers to the [LiveData] of the [CameraState] of + /// the current [camera], saved as [liveCameraState]. /// /// If a previous [liveCameraState] was stored, existing observers are /// removed, as well. Future _updateCameraInfoAndLiveCameraState(int cameraId) async { cameraInfo = await camera!.getCameraInfo(); + cameraControl = await camera!.getCameraControl(); await liveCameraState?.removeObservers(); liveCameraState = await cameraInfo!.getCameraState(); await liveCameraState!.observe(_createCameraClosingObserver(cameraId)); @@ -917,23 +1153,29 @@ class AndroidCameraCameraX extends CameraPlatform { ResolutionStrategy.fallbackRuleClosestLowerThenHigher; Size? boundSize; + int? aspectRatio; ResolutionStrategy? resolutionStrategy; switch (preset) { case ResolutionPreset.low: boundSize = const Size(320, 240); + aspectRatio = AspectRatio.ratio4To3; case ResolutionPreset.medium: boundSize = const Size(720, 480); case ResolutionPreset.high: boundSize = const Size(1280, 720); + aspectRatio = AspectRatio.ratio16To9; case ResolutionPreset.veryHigh: boundSize = const Size(1920, 1080); + aspectRatio = AspectRatio.ratio16To9; case ResolutionPreset.ultraHigh: boundSize = const Size(3840, 2160); + aspectRatio = AspectRatio.ratio16To9; case ResolutionPreset.max: // Automatically set strategy to choose highest available. resolutionStrategy = proxy.createResolutionStrategy(highestAvailable: true); - return proxy.createResolutionSelector(resolutionStrategy); + return proxy.createResolutionSelector(resolutionStrategy, + /* ResolutionFilter */ null, /* AspectRatioStrategy */ null); case null: // If no preset is specified, default to CameraX's default behavior // for each UseCase. @@ -942,7 +1184,14 @@ class AndroidCameraCameraX extends CameraPlatform { resolutionStrategy = proxy.createResolutionStrategy( boundSize: boundSize, fallbackRule: fallbackRule); - return proxy.createResolutionSelector(resolutionStrategy); + final ResolutionFilter resolutionFilter = + proxy.createResolutionFilterWithOnePreferredSize(boundSize); + final AspectRatioStrategy? aspectRatioStrategy = aspectRatio == null + ? null + : proxy.createAspectRatioStrategy( + aspectRatio, AspectRatioStrategy.fallbackRuleAuto); + return proxy.createResolutionSelector( + resolutionStrategy, resolutionFilter, aspectRatioStrategy); } /// Returns the [QualitySelector] that maps to the specified resolution @@ -981,4 +1230,108 @@ class AndroidCameraCameraX extends CameraPlatform { return proxy.createQualitySelector( videoQuality: videoQuality, fallbackStrategy: fallbackStrategy); } + + // Methods for configuring auto-focus and auto-exposure: + + Future _startFocusAndMeteringForPoint( + {required Point? point, + required int meteringMode, + bool disableAutoCancel = false}) async { + return _startFocusAndMeteringFor( + meteringPoint: point == null + ? null + : proxy.createMeteringPoint( + point.x, point.y, /* size */ null, cameraInfo!), + meteringMode: meteringMode, + disableAutoCancel: disableAutoCancel); + } + + /// Starts a focus and metering action and returns whether or not it was + /// successful. + /// + /// This method will modify and start the current action's [MeteringPoint]s + /// overriden with the [meteringPoint] provided for the specified + /// [meteringMode] type only, with all other metering points of other modes + /// left untouched. If no current action exists, only the specified + /// [meteringPoint] will be set. Thus, the focus and metering action started + /// will only contain at most the one most recently set metering point for + /// each metering mode: AF, AE, AWB. + /// + /// Thus, if [meteringPoint] is non-null, this action includes: + /// * metering points and their modes previously added to + /// [currentFocusMeteringAction] that do not share a metering mode with + /// [meteringPoint] (if [currentFocusMeteringAction] is non-null) and + /// * [meteringPoint] with the specified [meteringMode]. + /// If [meteringPoint] is null and [currentFocusMeteringAction] is non-null, + /// this action includes only metering points and their modes previously added + /// to [currentFocusMeteringAction] that do not share a metering mode with + /// [meteringPoint]. If [meteringPoint] and [currentFocusMeteringAction] are + /// null, then focus and metering will be canceled. + Future _startFocusAndMeteringFor( + {required MeteringPoint? meteringPoint, + required int meteringMode, + bool disableAutoCancel = false}) async { + if (meteringPoint == null) { + // Try to clear any metering point from previous action with the specified + // meteringMode. + if (currentFocusMeteringAction == null) { + // Attempting to clear a metering point from a previous action, but no + // such action exists. + return false; + } + + // Remove metering point with specified meteringMode from current focus + // and metering action, as only one focus or exposure point may be set + // at once in this plugin. + final List<(MeteringPoint, int?)> newMeteringPointInfos = + currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + // meteringPointInfo may technically include points without a + // mode specified, but this logic is safe because this plugin + // only uses points that explicitly have mode + // FocusMeteringAction.flagAe or FocusMeteringAction.flagAf. + meteringPointInfo.$2 != meteringMode) + .toList(); + + if (newMeteringPointInfos.isEmpty) { + // If no other metering points were specified, cancel any previously + // started focus and metering actions. + await cameraControl.cancelFocusAndMetering(); + currentFocusMeteringAction = null; + return true; + } + currentFocusMeteringAction = proxy.createFocusMeteringAction( + newMeteringPointInfos, disableAutoCancel); + } else if (meteringPoint.x < 0 || + meteringPoint.x > 1 || + meteringPoint.y < 0 || + meteringPoint.y > 1) { + throw CameraException('pointInvalid', + 'The coordinates of a metering point for an auto-focus or auto-exposure action must be within (0,0) and (1,1), but a point with coordinates (${meteringPoint.x}, ${meteringPoint.y}) was provided for metering mode $meteringMode.'); + } else { + // Add new metering point with specified meteringMode, which may involve + // replacing a metering point with the same specified meteringMode from + // the current focus and metering action. + List<(MeteringPoint, int?)> newMeteringPointInfos = + <(MeteringPoint, int?)>[]; + + if (currentFocusMeteringAction != null) { + newMeteringPointInfos = currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + // meteringPointInfo may technically include points without a + // mode specified, but this logic is safe because this plugin + // only uses points that explicitly have mode + // FocusMeteringAction.flagAe or FocusMeteringAction.flagAf. + meteringPointInfo.$2 != meteringMode) + .toList(); + } + newMeteringPointInfos.add((meteringPoint, meteringMode)); + currentFocusMeteringAction = proxy.createFocusMeteringAction( + newMeteringPointInfos, disableAutoCancel); + } + + final FocusMeteringResult? result = + await cameraControl.startFocusAndMetering(currentFocusMeteringAction!); + return await result?.isFocusSuccessful() ?? false; + } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera_control.dart b/packages/camera/camera_android_camerax/lib/src/camera_control.dart index 9c50ffa161fd..0a307f3afc4a 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_control.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_control.dart @@ -56,6 +56,10 @@ class CameraControl extends JavaObject { /// Will trigger an auto focus action and enable auto focus/auto exposure/ /// auto white balance metering regions. /// + /// Only one [FocusMeteringAction] is allowed to run at a time; if multiple + /// are executed in a row, only the latest one will work and other actions + /// will be canceled. + /// /// Returns null if focus and metering could not be started. Future startFocusAndMetering( FocusMeteringAction action) { @@ -74,6 +78,10 @@ class CameraControl extends JavaObject { /// of the current [ExposureState]'s `exposureCompensationRange` for the call /// to succeed. /// + /// Only one [setExposureCompensationIndex] is allowed to run at a time; if + /// multiple are executed in a row, only the latest setting will be kept in + /// the camera. + /// /// Returns null if the exposure compensation index failed to be set. Future setExposureCompensationIndex(int index) async { return _api.setExposureCompensationIndexFromInstance(this, index); @@ -133,14 +141,21 @@ class _CameraControlHostApiImpl extends CameraControlHostApi { instanceManager.getIdentifier(instance)!; final int actionIdentifier = instanceManager.getIdentifier(action)!; try { - final int focusMeteringResultId = await startFocusAndMetering( + final int? focusMeteringResultId = await startFocusAndMetering( cameraControlIdentifier, actionIdentifier); + if (focusMeteringResultId == null) { + SystemServices.cameraErrorStreamController.add( + 'Starting focus and metering was canceled due to the camera being closed or a new request being submitted.'); + return Future.value(); + } return instanceManager.getInstanceWithWeakReference( focusMeteringResultId); } on PlatformException catch (e) { SystemServices.cameraErrorStreamController .add(e.message ?? 'Starting focus and metering failed.'); - return Future.value(); + // Surfacing error to differentiate an operation cancellation from an + // illegal argument exception at a plugin layer. + rethrow; } } @@ -158,11 +173,20 @@ class _CameraControlHostApiImpl extends CameraControlHostApi { CameraControl instance, int index) async { final int identifier = instanceManager.getIdentifier(instance)!; try { - return setExposureCompensationIndex(identifier, index); + final int? exposureCompensationIndex = + await setExposureCompensationIndex(identifier, index); + if (exposureCompensationIndex == null) { + SystemServices.cameraErrorStreamController.add( + 'Setting exposure compensation index was canceled due to the camera being closed or a new request being submitted.'); + return Future.value(); + } + return exposureCompensationIndex; } on PlatformException catch (e) { SystemServices.cameraErrorStreamController.add(e.message ?? 'Setting the camera exposure compensation index failed.'); - return Future.value(); + // Surfacing error to plugin layer to maintain consistency of + // setExposureOffset implementation across platform implementations. + rethrow; } } } diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index 07515c0c14fd..ac66dfd0ae77 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -27,14 +27,14 @@ enum CameraStateType { /// If you need to add another type to support a type S to use a LiveData in /// this plugin, ensure the following is done on the Dart side: /// -/// * In `../lib/src/live_data.dart`, add new cases for S in +/// * In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in /// `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of /// type S from a LiveData instance and in `LiveDataFlutterApiImpl#create` /// to create the expected type of LiveData when requested. /// /// On the native side, ensure the following is done: /// -/// * Update `LiveDataHostApiImpl#getValue` is updated to properly return +/// * Make sure `LiveDataHostApiImpl#getValue` is updated to properly return /// identifiers for instances of type S. /// * Update `ObserverFlutterApiWrapper#onChanged` to properly handle receiving /// calls with instances of type S if a LiveData instance is observed. @@ -68,6 +68,24 @@ enum VideoResolutionFallbackRule { lowerQualityThan, } +/// The types of capture request options this plugin currently supports. +/// +/// If you need to add another option to support, ensure the following is done +/// on the Dart side: +/// +/// * In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this +/// option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` +/// to create the expected Map entry of option key index and value to send to +/// the native side. +/// +/// On the native side, ensure the following is done: +/// +/// * Update `CaptureRequestOptionsHostApiImpl#create` to set the correct +/// `CaptureRequest` key with a valid value type for this option. +/// +/// See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest +/// for the sorts of capture request options that can be supported via CameraX's +/// interoperability with Camera2. enum CaptureRequestKeySupportedType { controlAeLock, } @@ -1896,7 +1914,10 @@ class ResolutionSelectorHostApi { static const MessageCodec codec = StandardMessageCodec(); - Future create(int arg_identifier, int? arg_resolutionStrategyIdentifier, + Future create( + int arg_identifier, + int? arg_resolutionStrategyIdentifier, + int? arg_resolutionSelectorIdentifier, int? arg_aspectRatioStrategyIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ResolutionSelectorHostApi.create', codec, @@ -1904,6 +1925,7 @@ class ResolutionSelectorHostApi { final List? replyList = await channel.send([ arg_identifier, arg_resolutionStrategyIdentifier, + arg_resolutionSelectorIdentifier, arg_aspectRatioStrategyIdentifier ]) as List?; if (replyList == null) { @@ -2884,7 +2906,7 @@ class CameraControlHostApi { } } - Future startFocusAndMetering( + Future startFocusAndMetering( int arg_identifier, int arg_focusMeteringActionId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering', codec, @@ -2903,13 +2925,8 @@ class CameraControlHostApi { message: replyList[1] as String?, details: replyList[2], ); - } else if (replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); } else { - return (replyList[0] as int?)!; + return (replyList[0] as int?); } } @@ -2935,7 +2952,7 @@ class CameraControlHostApi { } } - Future setExposureCompensationIndex( + Future setExposureCompensationIndex( int arg_identifier, int arg_index) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex', @@ -2954,13 +2971,8 @@ class CameraControlHostApi { message: replyList[1] as String?, details: replyList[2], ); - } else if (replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); } else { - return (replyList[0] as int?)!; + return (replyList[0] as int?); } } } @@ -3027,14 +3039,18 @@ class FocusMeteringActionHostApi { static const MessageCodec codec = _FocusMeteringActionHostApiCodec(); - Future create(int arg_identifier, - List arg_meteringPointInfos) async { + Future create( + int arg_identifier, + List arg_meteringPointInfos, + bool? arg_disableAutoCancel) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FocusMeteringActionHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_identifier, arg_meteringPointInfos]) - as List?; + final List? replyList = await channel.send([ + arg_identifier, + arg_meteringPointInfos, + arg_disableAutoCancel + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -3130,14 +3146,14 @@ class MeteringPointHostApi { static const MessageCodec codec = StandardMessageCodec(); - Future create( - int arg_identifier, double arg_x, double arg_y, double? arg_size) async { + Future create(int arg_identifier, double arg_x, double arg_y, + double? arg_size, int arg_cameraInfoId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.MeteringPointHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_identifier, arg_x, arg_y, arg_size]) - as List?; + final List? replyList = await channel.send( + [arg_identifier, arg_x, arg_y, arg_size, arg_cameraInfoId]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -3328,3 +3344,62 @@ class Camera2CameraControlHostApi { } } } + +class _ResolutionFilterHostApiCodec extends StandardMessageCodec { + const _ResolutionFilterHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ResolutionInfo) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ResolutionInfo.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ResolutionFilterHostApi { + /// Constructor for [ResolutionFilterHostApi]. 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. + ResolutionFilterHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _ResolutionFilterHostApiCodec(); + + Future createWithOnePreferredSize( + int arg_identifier, ResolutionInfo arg_preferredResolution) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ResolutionFilterHostApi.createWithOnePreferredSize', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier, arg_preferredResolution]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart index 87d3680ff2c3..6fec50ce3983 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -5,19 +5,27 @@ import 'dart:ui' show Size; import 'analyzer.dart'; +import 'aspect_ratio_strategy.dart'; +import 'camera2_camera_control.dart'; +import 'camera_control.dart'; +import 'camera_info.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; +import 'capture_request_options.dart'; import 'device_orientation_manager.dart'; import 'fallback_strategy.dart'; +import 'focus_metering_action.dart'; import 'image_analysis.dart'; import 'image_capture.dart'; import 'image_proxy.dart'; +import 'metering_point.dart'; import 'observer.dart'; import 'preview.dart'; import 'process_camera_provider.dart'; import 'quality_selector.dart'; import 'recorder.dart'; +import 'resolution_filter.dart'; import 'resolution_selector.dart'; import 'resolution_strategy.dart'; import 'system_services.dart'; @@ -49,6 +57,13 @@ class CameraXProxy { _startListeningForDeviceOrientationChange, this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider, this.getDefaultDisplayRotation = _getDefaultDisplayRotation, + this.getCamera2CameraControl = _getCamera2CameraControl, + this.createCaptureRequestOptions = _createAttachedCaptureRequestOptions, + this.createMeteringPoint = _createAttachedMeteringPoint, + this.createFocusMeteringAction = _createAttachedFocusMeteringAction, + this.createAspectRatioStrategy = _createAttachedAspectRatioStrategy, + this.createResolutionFilterWithOnePreferredSize = + _createAttachedResolutionFilterWithOnePreferredSize, }); /// Returns a [ProcessCameraProvider] instance. @@ -105,9 +120,11 @@ class CameraXProxy { int? fallbackRule}) createResolutionStrategy; /// Returns a [ResolutionSelector] configured with the specified - /// [ResolutionStrategy]. - ResolutionSelector Function(ResolutionStrategy resolutionStrategy) - createResolutionSelector; + /// [ResolutionStrategy], [ResolutionFilter], and [AspectRatioStrategy]. + ResolutionSelector Function( + ResolutionStrategy resolutionStrategy, + ResolutionFilter? resolutionFilter, + AspectRatioStrategy? aspectRatioStrategy) createResolutionSelector; /// Returns a [FallbackStrategy] configured with the specified [VideoQuality] /// and [VideoResolutionFallbackRule]. @@ -137,6 +154,35 @@ class CameraXProxy { /// rotation constants. Future Function() getDefaultDisplayRotation; + /// Get [Camera2CameraControl] instance from [cameraControl]. + Camera2CameraControl Function(CameraControl cameraControl) + getCamera2CameraControl; + + /// Creates a [CaptureRequestOptions] with specified options. + CaptureRequestOptions Function( + List<(CaptureRequestKeySupportedType, Object?)> options) + createCaptureRequestOptions; + + /// Returns a [MeteringPoint] with the specified coordinates based on + /// [cameraInfo]. + MeteringPoint Function( + double x, double y, double? size, CameraInfo cameraInfo) + createMeteringPoint; + + /// Returns a [FocusMeteringAction] based on the specified metering points + /// and their modes. + FocusMeteringAction Function(List<(MeteringPoint, int?)> meteringPointInfos, + bool? disableAutoCancel) createFocusMeteringAction; + + /// Creates an [AspectRatioStrategy] with specified aspect ratio and fallback + /// rule. + AspectRatioStrategy Function(int aspectRatio, int fallbackRule) + createAspectRatioStrategy; + + /// Creates a [ResolutionFilter] that prioritizes specified resolution. + ResolutionFilter Function(Size preferredResolution) + createResolutionFilterWithOnePreferredSize; + static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); } @@ -204,8 +250,13 @@ class CameraXProxy { } static ResolutionSelector _createAttachedResolutionSelector( - ResolutionStrategy resolutionStrategy) { - return ResolutionSelector(resolutionStrategy: resolutionStrategy); + ResolutionStrategy resolutionStrategy, + ResolutionFilter? resolutionFilter, + AspectRatioStrategy? aspectRatioStrategy) { + return ResolutionSelector( + resolutionStrategy: resolutionStrategy, + resolutionFilter: resolutionFilter, + aspectRatioStrategy: aspectRatioStrategy); } static FallbackStrategy _createAttachedFallbackStrategy( @@ -239,4 +290,38 @@ class CameraXProxy { static Future _getDefaultDisplayRotation() async { return DeviceOrientationManager.getDefaultDisplayRotation(); } + + static Camera2CameraControl _getCamera2CameraControl( + CameraControl cameraControl) { + return Camera2CameraControl(cameraControl: cameraControl); + } + + static CaptureRequestOptions _createAttachedCaptureRequestOptions( + List<(CaptureRequestKeySupportedType, Object?)> options) { + return CaptureRequestOptions(requestedOptions: options); + } + + static MeteringPoint _createAttachedMeteringPoint( + double x, double y, double? size, CameraInfo cameraInfo) { + return MeteringPoint(x: x, y: y, size: size, cameraInfo: cameraInfo); + } + + static FocusMeteringAction _createAttachedFocusMeteringAction( + List<(MeteringPoint, int?)> meteringPointInfos, bool? disableAutoCancel) { + return FocusMeteringAction( + meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel); + } + + static AspectRatioStrategy _createAttachedAspectRatioStrategy( + int preferredAspectRatio, int fallbackRule) { + return AspectRatioStrategy( + preferredAspectRatio: preferredAspectRatio, fallbackRule: fallbackRule); + } + + static ResolutionFilter _createAttachedResolutionFilterWithOnePreferredSize( + Size preferredSize) { + return ResolutionFilter.onePreferredSize( + preferredResolution: preferredSize); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart index 6d9ebd97f015..81fa6f5abf14 100644 --- a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart +++ b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart @@ -19,15 +19,15 @@ class FocusMeteringAction extends JavaObject { FocusMeteringAction({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, - required List<(MeteringPoint meteringPoint, int? meteringMode)> - meteringPointInfos, + required this.meteringPointInfos, + this.disableAutoCancel, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { _api = _FocusMeteringActionHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createFromInstance(this, meteringPointInfos); + _api.createFromInstance(this, meteringPointInfos, disableAutoCancel); } /// Creates a [FocusMeteringAction] that is not automatically attached to a @@ -35,6 +35,8 @@ class FocusMeteringAction extends JavaObject { FocusMeteringAction.detached({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, + required this.meteringPointInfos, + this.disableAutoCancel, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, @@ -45,6 +47,17 @@ class FocusMeteringAction extends JavaObject { late final _FocusMeteringActionHostApiImpl _api; + /// The requested [MeteringPoint]s and modes that are relevant to each of those + /// points. + final List<(MeteringPoint meteringPoint, int? meteringMode)> + meteringPointInfos; + + /// Disables the auto-cancel. + /// + /// By default (and if set to false), auto-cancel is enabled with 5 seconds + /// duration. + final bool? disableAutoCancel; + /// Flag for metering mode that indicates the auto focus region is enabled. /// /// An autofocus scan is also triggered when [flagAf] is assigned. @@ -92,12 +105,14 @@ class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi { /// [MeteringPoint]s and their modes in order of descending priority. void createFromInstance( FocusMeteringAction instance, - List<(MeteringPoint meteringPoint, int? meteringMode)> - meteringPointInfos) { + List<(MeteringPoint meteringPoint, int? meteringMode)> meteringPointInfos, + bool? disableAutoCancel) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (FocusMeteringAction original) { return FocusMeteringAction.detached( - binaryMessenger: binaryMessenger, instanceManager: instanceManager); + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + meteringPointInfos: original.meteringPointInfos); }); final List meteringPointInfosWithIds = @@ -111,6 +126,6 @@ class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi { meteringMode: meteringPointInfo.$2)); } - create(identifier, meteringPointInfosWithIds); + create(identifier, meteringPointInfosWithIds, disableAutoCancel); } } diff --git a/packages/camera/camera_android_camerax/lib/src/metering_point.dart b/packages/camera/camera_android_camerax/lib/src/metering_point.dart index d699993a8933..c5ee6061de9c 100644 --- a/packages/camera/camera_android_camerax/lib/src/metering_point.dart +++ b/packages/camera/camera_android_camerax/lib/src/metering_point.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart' show BinaryMessenger; import 'package:meta/meta.dart' show immutable; import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera_info.dart'; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; @@ -23,13 +24,14 @@ class MeteringPoint extends JavaObject { required this.x, required this.y, this.size, + required this.cameraInfo, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { _api = _MeteringPointHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createFromInstance(this, x, y, size); + _api.createFromInstance(this, x, y, size, cameraInfo); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } @@ -41,6 +43,7 @@ class MeteringPoint extends JavaObject { required this.x, required this.y, this.size, + required this.cameraInfo, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, @@ -63,6 +66,10 @@ class MeteringPoint extends JavaObject { /// region width/height if crop region is set). final double? size; + /// The [CameraInfo] used to construct the metering point with a display- + /// oriented metering point factory. + final CameraInfo cameraInfo; + /// The default size of the [MeteringPoint] width and height (ranging from 0 /// to 1) which is a (normalized) percentage of the sensor width/height (or /// crop region width/height if crop region is set). @@ -100,8 +107,8 @@ class _MeteringPointHostApiImpl extends MeteringPointHostApi { /// Creates a [MeteringPoint] instance with the specified [x] and [y] /// coordinates as well as [size] if non-null. - Future createFromInstance( - MeteringPoint instance, double x, double y, double? size) { + Future createFromInstance(MeteringPoint instance, double x, double y, + double? size, CameraInfo cameraInfo) { int? identifier = instanceManager.getIdentifier(instance); identifier ??= instanceManager.addDartCreatedInstance(instance, onCopy: (MeteringPoint original) { @@ -110,9 +117,11 @@ class _MeteringPointHostApiImpl extends MeteringPointHostApi { instanceManager: instanceManager, x: original.x, y: original.y, + cameraInfo: original.cameraInfo, size: original.size); }); + final int? camInfoId = instanceManager.getIdentifier(cameraInfo); - return create(identifier, x, y, size); + return create(identifier, x, y, size, camInfoId!); } } diff --git a/packages/camera/camera_android_camerax/lib/src/resolution_filter.dart b/packages/camera/camera_android_camerax/lib/src/resolution_filter.dart new file mode 100644 index 000000000000..3a428a00e73a --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/resolution_filter.dart @@ -0,0 +1,107 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Filterer for applications to specify preferred resolutions. +/// +/// This is an indirect wrapping of the native Android `ResolutionFilter`, +/// an interface that requires a synchronous response. Achieving such is not +/// possible through pigeon. Thus, constructing a [ResolutionFilter] with a +/// particular constructor will create a native `ResolutionFilter` with the +/// characteristics described in the documentation for that constructor, +/// respectively. +/// +/// If the provided constructors do not meet your needs, feel free to add a new +/// constructor; see CONTRIBUTING.MD for more information on how to do so. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ResolutionFilter/ResolutionFilter. +@immutable +class ResolutionFilter extends JavaObject { + /// Constructs a [ResolutionFilter]. + /// + /// This will construct a native `ResolutionFilter` that will prioritize the + /// specified [preferredResolution] (if supported) over other supported + /// resolutions, whose priorities (as determined by CameraX) will remain the + /// same. + ResolutionFilter.onePreferredSize({ + required this.preferredResolution, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionFilterHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached() { + _api.createWithOnePreferredSizeFromInstances(this, preferredResolution); + } + + /// Instantiates a [ResolutionFilter.onePreferredSize] that is not + /// automatically attached to a native object. + ResolutionFilter.onePreferredSizeDetached({ + required this.preferredResolution, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionFilterHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached(); + + final _ResolutionFilterHostApiImpl _api; + + /// The resolution for a [ResolutionFilter.onePreferredSize] to prioritize. + final Size preferredResolution; +} + +/// Host API implementation of [ResolutionFilter]. +class _ResolutionFilterHostApiImpl extends ResolutionFilterHostApi { + /// Constructs an [_ResolutionFilterHostApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + _ResolutionFilterHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [ResolutionFilter] on the native side that will prioritize + /// the specified [preferredResolution]. + Future createWithOnePreferredSizeFromInstances( + ResolutionFilter instance, + Size preferredResolution, + ) { + return createWithOnePreferredSize( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (ResolutionFilter original) => + ResolutionFilter.onePreferredSizeDetached( + preferredResolution: original.preferredResolution, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + ResolutionInfo( + width: preferredResolution.width.toInt(), + height: preferredResolution.height.toInt(), + ), + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart b/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart index 74191724e5ce..3017daab811a 100644 --- a/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart +++ b/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart @@ -9,6 +9,7 @@ import 'aspect_ratio_strategy.dart'; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'resolution_filter.dart'; import 'resolution_strategy.dart'; /// A set of requirements and priorities used to select a resolution for a @@ -20,6 +21,7 @@ class ResolutionSelector extends JavaObject { /// Construct a [ResolutionSelector]. ResolutionSelector({ this.resolutionStrategy, + this.resolutionFilter, this.aspectRatioStrategy, super.binaryMessenger, super.instanceManager, @@ -28,7 +30,8 @@ class ResolutionSelector extends JavaObject { binaryMessenger: binaryMessenger, ), super.detached() { - _api.createFromInstances(this, resolutionStrategy, aspectRatioStrategy); + _api.createFromInstances( + this, resolutionStrategy, resolutionFilter, aspectRatioStrategy); } /// Instantiates a [ResolutionSelector] without creating and attaching to an @@ -38,6 +41,7 @@ class ResolutionSelector extends JavaObject { /// library or to create a copy for an [InstanceManager]. ResolutionSelector.detached({ this.resolutionStrategy, + this.resolutionFilter, this.aspectRatioStrategy, super.binaryMessenger, super.instanceManager, @@ -53,6 +57,9 @@ class ResolutionSelector extends JavaObject { /// image. final ResolutionStrategy? resolutionStrategy; + /// Filter for CameraX to automatically select a desirable resolution. + final ResolutionFilter? resolutionFilter; + /// Determines how the UseCase will choose the aspect ratio of the captured /// image. final AspectRatioStrategy? aspectRatioStrategy; @@ -81,10 +88,12 @@ class _ResolutionSelectorHostApiImpl extends ResolutionSelectorHostApi { final InstanceManager instanceManager; /// Creates a [ResolutionSelector] on the native side with the - /// [ResolutionStrategy] and [AspectRatioStrategy] if specified. + /// [ResolutionStrategy], [ResolutionFilter], and [AspectRatioStrategy] if + /// specified. Future createFromInstances( ResolutionSelector instance, ResolutionStrategy? resolutionStrategy, + ResolutionFilter? resolutionFilter, AspectRatioStrategy? aspectRatioStrategy, ) { return create( @@ -100,6 +109,9 @@ class _ResolutionSelectorHostApiImpl extends ResolutionSelectorHostApi { resolutionStrategy == null ? null : instanceManager.getIdentifier(resolutionStrategy)!, + resolutionFilter == null + ? null + : instanceManager.getIdentifier(resolutionFilter)!, aspectRatioStrategy == null ? null : instanceManager.getIdentifier(aspectRatioStrategy)!, diff --git a/packages/camera/camera_android_camerax/lib/src/zoom_state.dart b/packages/camera/camera_android_camerax/lib/src/zoom_state.dart index 4c93d41e0249..6fe5321389b6 100644 --- a/packages/camera/camera_android_camerax/lib/src/zoom_state.dart +++ b/packages/camera/camera_android_camerax/lib/src/zoom_state.dart @@ -15,7 +15,7 @@ import 'java_object.dart'; /// See https://developer.android.com/reference/androidx/camera/core/ZoomState. @immutable class ZoomState extends JavaObject { - /// Constructs a [CameraInfo] that is not automatically attached to a native object. + /// Constructs a [ZoomState] that is not automatically attached to a native object. ZoomState.detached( {super.binaryMessenger, super.instanceManager, diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index db6164044d30..18741904a8d0 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -66,7 +66,7 @@ class CameraStateTypeData { /// If you need to add another type to support a type S to use a LiveData in /// this plugin, ensure the following is done on the Dart side: /// -/// * In `../lib/src/live_data.dart`, add new cases for S in +/// * In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in /// `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of /// type S from a LiveData instance and in `LiveDataFlutterApiImpl#create` /// to create the expected type of LiveData when requested. @@ -148,7 +148,7 @@ class MeteringPointInfo { /// If you need to add another option to support, ensure the following is done /// on the Dart side: /// -/// * In `../lib/src/capture_request_options.dart`, add new cases for this +/// * In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this /// option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` /// to create the expected Map entry of option key index and value to send to /// the native side. @@ -366,6 +366,7 @@ abstract class ResolutionSelectorHostApi { void create( int identifier, int? resolutionStrategyIdentifier, + int? resolutionSelectorIdentifier, int? aspectRatioStrategyIdentifier, ); } @@ -485,13 +486,13 @@ abstract class CameraControlHostApi { void setZoomRatio(int identifier, double ratio); @async - int startFocusAndMetering(int identifier, int focusMeteringActionId); + int? startFocusAndMetering(int identifier, int focusMeteringActionId); @async void cancelFocusAndMetering(int identifier); @async - int setExposureCompensationIndex(int identifier, int index); + int? setExposureCompensationIndex(int identifier, int index); } @FlutterApi() @@ -501,7 +502,8 @@ abstract class CameraControlFlutterApi { @HostApi(dartHostTestHandler: 'TestFocusMeteringActionHostApi') abstract class FocusMeteringActionHostApi { - void create(int identifier, List meteringPointInfos); + void create(int identifier, List meteringPointInfos, + bool? disableAutoCancel); } @HostApi(dartHostTestHandler: 'TestFocusMeteringResultHostApi') @@ -516,7 +518,8 @@ abstract class FocusMeteringResultFlutterApi { @HostApi(dartHostTestHandler: 'TestMeteringPointHostApi') abstract class MeteringPointHostApi { - void create(int identifier, double x, double y, double? size); + void create( + int identifier, double x, double y, double? size, int cameraInfoId); double getDefaultPointSize(); } @@ -534,3 +537,9 @@ abstract class Camera2CameraControlHostApi { void addCaptureRequestOptions( int identifier, int captureRequestOptionsIdentifier); } + +@HostApi(dartHostTestHandler: 'TestResolutionFilterHostApi') +abstract class ResolutionFilterHostApi { + void createWithOnePreferredSize( + int identifier, ResolutionInfo preferredResolution); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index d6970d2e559e..cb53c9d6c636 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.5.0+32 +version: 0.6.2 environment: sdk: ^3.1.0 @@ -22,8 +22,6 @@ dependencies: camera_platform_interface: ^2.3.2 flutter: sdk: flutter - integration_test: - sdk: flutter meta: ^1.7.0 stream_transform: ^2.1.0 @@ -31,6 +29,8 @@ dev_dependencies: build_runner: ^2.2.0 flutter_test: sdk: flutter + integration_test: + sdk: flutter mockito: 5.4.4 pigeon: ^9.1.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index bc6a0d1c6a3b..f08a2e02ad49 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -3,11 +3,14 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math' show Point; import 'package:async/async.dart'; import 'package:camera_android_camerax/camera_android_camerax.dart'; import 'package:camera_android_camerax/src/analyzer.dart'; +import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart'; import 'package:camera_android_camerax/src/camera.dart'; +import 'package:camera_android_camerax/src/camera2_camera_control.dart'; import 'package:camera_android_camerax/src/camera_control.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; @@ -15,13 +18,17 @@ import 'package:camera_android_camerax/src/camera_state.dart'; import 'package:camera_android_camerax/src/camera_state_error.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/camerax_proxy.dart'; +import 'package:camera_android_camerax/src/capture_request_options.dart'; import 'package:camera_android_camerax/src/device_orientation_manager.dart'; import 'package:camera_android_camerax/src/exposure_state.dart'; import 'package:camera_android_camerax/src/fallback_strategy.dart'; +import 'package:camera_android_camerax/src/focus_metering_action.dart'; +import 'package:camera_android_camerax/src/focus_metering_result.dart'; import 'package:camera_android_camerax/src/image_analysis.dart'; import 'package:camera_android_camerax/src/image_capture.dart'; import 'package:camera_android_camerax/src/image_proxy.dart'; import 'package:camera_android_camerax/src/live_data.dart'; +import 'package:camera_android_camerax/src/metering_point.dart'; import 'package:camera_android_camerax/src/observer.dart'; import 'package:camera_android_camerax/src/pending_recording.dart'; import 'package:camera_android_camerax/src/plane_proxy.dart'; @@ -30,6 +37,7 @@ import 'package:camera_android_camerax/src/process_camera_provider.dart'; import 'package:camera_android_camerax/src/quality_selector.dart'; import 'package:camera_android_camerax/src/recorder.dart'; import 'package:camera_android_camerax/src/recording.dart'; +import 'package:camera_android_camerax/src/resolution_filter.dart'; import 'package:camera_android_camerax/src/resolution_selector.dart'; import 'package:camera_android_camerax/src/resolution_strategy.dart'; import 'package:camera_android_camerax/src/surface.dart'; @@ -38,8 +46,9 @@ import 'package:camera_android_camerax/src/use_case.dart'; import 'package:camera_android_camerax/src/video_capture.dart'; import 'package:camera_android_camerax/src/zoom_state.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/services.dart' show DeviceOrientation, Uint8List; -import 'package:flutter/widgets.dart'; +import 'package:flutter/services.dart' + show DeviceOrientation, PlatformException, Uint8List; +import 'package:flutter/widgets.dart' show BuildContext, Size, Texture, Widget; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -49,13 +58,16 @@ import 'test_camerax_library.g.dart'; @GenerateNiceMocks(>[ MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -66,6 +78,7 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -113,6 +126,115 @@ void main() { return cameraClosingEventSent && cameraErrorSent; } + /// CameraXProxy for testing functionality related to the camera resolution + /// preset (setting expected ResolutionSelectors, QualitySelectors, etc.). + CameraXProxy getProxyForTestingResolutionPreset( + MockProcessCameraProvider mockProcessCameraProvider) => + CameraXProxy( + getProcessCameraProvider: () => + Future.value(mockProcessCameraProvider), + createCameraSelector: (int cameraSelectorLensDirection) => + MockCameraSelector(), + createPreview: + (ResolutionSelector? resolutionSelector, int? targetRotation) => + Preview.detached( + initialTargetRotation: targetRotation, + resolutionSelector: resolutionSelector), + createImageCapture: + (ResolutionSelector? resolutionSelector, int? targetRotation) => + ImageCapture.detached( + resolutionSelector: resolutionSelector, + initialTargetRotation: targetRotation), + createRecorder: (QualitySelector? qualitySelector) => + Recorder.detached(qualitySelector: qualitySelector), + createVideoCapture: (_) => + Future.value(MockVideoCapture()), + createImageAnalysis: + (ResolutionSelector? resolutionSelector, int? targetRotation) => + ImageAnalysis.detached( + resolutionSelector: resolutionSelector, + initialTargetRotation: targetRotation), + createResolutionStrategy: ( + {bool highestAvailable = false, + Size? boundSize, + int? fallbackRule}) { + if (highestAvailable) { + return ResolutionStrategy.detachedHighestAvailableStrategy(); + } + return ResolutionStrategy.detached( + boundSize: boundSize, fallbackRule: fallbackRule); + }, + createResolutionSelector: (ResolutionStrategy resolutionStrategy, + ResolutionFilter? resolutionFilter, + AspectRatioStrategy? aspectRatioStrategy) => + ResolutionSelector.detached( + resolutionStrategy: resolutionStrategy, + resolutionFilter: resolutionFilter, + aspectRatioStrategy: aspectRatioStrategy), + createFallbackStrategy: ( + {required VideoQuality quality, + required VideoResolutionFallbackRule fallbackRule}) => + FallbackStrategy.detached( + quality: quality, fallbackRule: fallbackRule), + createQualitySelector: ( + {required VideoQuality videoQuality, + required FallbackStrategy fallbackStrategy}) => + QualitySelector.detached(qualityList: [ + VideoQualityData(quality: videoQuality) + ], fallbackStrategy: fallbackStrategy), + createCameraStateObserver: (_) => MockObserver(), + requestCameraPermissions: (_) => Future.value(), + startListeningForDeviceOrientationChange: (_, __) {}, + setPreviewSurfaceProvider: (_) => Future.value( + 3), // 3 is a random Flutter SurfaceTexture ID for testing, + createAspectRatioStrategy: (int aspectRatio, int fallbackRule) => + AspectRatioStrategy.detached( + preferredAspectRatio: aspectRatio, fallbackRule: fallbackRule), + createResolutionFilterWithOnePreferredSize: + (Size preferredResolution) => + ResolutionFilter.onePreferredSizeDetached( + preferredResolution: preferredResolution), + ); + + /// CameraXProxy for testing exposure and focus related controls. + /// + /// Modifies the creation of [MeteringPoint]s and [FocusMeteringAction]s to + /// return objects detached from a native object. + CameraXProxy getProxyForExposureAndFocus() => CameraXProxy( + createMeteringPoint: + (double x, double y, double? size, CameraInfo cameraInfo) => + MeteringPoint.detached( + x: x, y: y, size: size, cameraInfo: cameraInfo), + createFocusMeteringAction: + (List<(MeteringPoint, int?)> meteringPointInfos, + bool? disableAutoCancel) => + FocusMeteringAction.detached( + meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel), + ); + + /// CameraXProxy for testing setting focus and exposure points. + /// + /// Modifies the retrieval of a [Camera2CameraControl] instance to depend on + /// interaction with expected [cameraControl] instance and modifies creation + /// of [CaptureRequestOptions] to return objects detached from a native object. + CameraXProxy getProxyForSettingFocusandExposurePoints( + CameraControl cameraControlForComparison, + Camera2CameraControl camera2cameraControl) { + final CameraXProxy proxy = getProxyForExposureAndFocus(); + + proxy.getCamera2CameraControl = (CameraControl cameraControl) => + cameraControl == cameraControlForComparison + ? camera2cameraControl + : Camera2CameraControl.detached(cameraControl: cameraControl); + + proxy.createCaptureRequestOptions = + (List<(CaptureRequestKeySupportedType, Object?)> options) => + CaptureRequestOptions.detached(requestedOptions: options); + + return proxy; + } + test('Should fetch CameraDescription instances for available cameras', () async { // Arrange @@ -241,7 +363,7 @@ void main() { Size? boundSize, int? fallbackRule}) => MockResolutionStrategy(), - createResolutionSelector: (_) => MockResolutionSelector(), + createResolutionSelector: (_, __, ___) => MockResolutionSelector(), createFallbackStrategy: ( {required VideoQuality quality, required VideoResolutionFallbackRule fallbackRule}) => @@ -259,6 +381,8 @@ void main() { startListeningForDeviceOrientationChange: (_, __) { startedListeningForDeviceOrientationChanges = true; }, + createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), + createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), ); when(mockPreview.setSurfaceProvider()) @@ -331,6 +455,7 @@ void main() { final MockVideoCapture mockVideoCapture = MockVideoCapture(); final MockCamera mockCamera = MockCamera(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCameraControl mockCameraControl = MockCameraControl(); // Tell plugin to create mock/detached objects and stub method calls for the // testing of createCamera. @@ -356,7 +481,7 @@ void main() { Size? boundSize, int? fallbackRule}) => MockResolutionStrategy(), - createResolutionSelector: (_) => MockResolutionSelector(), + createResolutionSelector: (_, __, ___) => MockResolutionSelector(), createFallbackStrategy: ( {required VideoQuality quality, required VideoResolutionFallbackRule fallbackRule}) => @@ -369,6 +494,8 @@ void main() { Observer.detached(onChanged: onChanged), requestCameraPermissions: (_) => Future.value(), startListeningForDeviceOrientationChange: (_, __) {}, + createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), + createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), ); when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, @@ -377,6 +504,8 @@ void main() { when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera.getCameraControl()) + .thenAnswer((_) async => mockCameraControl); camera.processCameraProvider = mockProcessCameraProvider; await camera.createCamera(testCameraDescription, testResolutionPreset, @@ -389,13 +518,16 @@ void main() { // Verify the camera's CameraInfo instance got updated. expect(camera.cameraInfo, equals(mockCameraInfo)); + // Verify camera's CameraControl instance got updated. + expect(camera.cameraControl, equals(mockCameraControl)); + // Verify preview has been marked as bound to the camera lifecycle by // createCamera. expect(camera.previewInitiallyBound, isTrue); }); test( - 'createCamera properly sets preset resolution for non-video capture use cases', + 'createCamera properly sets preset resolution selection strategy for non-video capture use cases', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const CameraLensDirection testLensDirection = CameraLensDirection.back; @@ -412,69 +544,13 @@ void main() { final MockProcessCameraProvider mockProcessCameraProvider = MockProcessCameraProvider(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); - final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); - final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); - final MockVideoCapture mockVideoCapture = MockVideoCapture(); - final MockRecorder mockRecorder = MockRecorder(); // Tell plugin to create mock/detached objects for testing createCamera // as needed. - camera.proxy = CameraXProxy( - getProcessCameraProvider: () => - Future.value(mockProcessCameraProvider), - createCameraSelector: (int cameraSelectorLensDirection) { - switch (cameraSelectorLensDirection) { - case CameraSelector.lensFacingFront: - return mockFrontCameraSelector; - case CameraSelector.lensFacingBack: - default: - return mockBackCameraSelector; - } - }, - createPreview: - (ResolutionSelector? resolutionSelector, int? targetRotation) => - Preview.detached( - initialTargetRotation: targetRotation, - resolutionSelector: resolutionSelector), - createImageCapture: - (ResolutionSelector? resolutionSelector, int? targetRotation) => - ImageCapture.detached( - resolutionSelector: resolutionSelector, - initialTargetRotation: targetRotation), - createRecorder: (_) => mockRecorder, - createVideoCapture: (_) => Future.value(mockVideoCapture), - createImageAnalysis: - (ResolutionSelector? resolutionSelector, int? targetRotation) => - ImageAnalysis.detached( - resolutionSelector: resolutionSelector, - initialTargetRotation: targetRotation), - createResolutionStrategy: ( - {bool highestAvailable = false, Size? boundSize, int? fallbackRule}) { - if (highestAvailable) { - return ResolutionStrategy.detachedHighestAvailableStrategy(); - } - - return ResolutionStrategy.detached( - boundSize: boundSize, fallbackRule: fallbackRule); - }, - createResolutionSelector: (ResolutionStrategy resolutionStrategy) => - ResolutionSelector.detached(resolutionStrategy: resolutionStrategy), - createFallbackStrategy: ( - {required VideoQuality quality, - required VideoResolutionFallbackRule fallbackRule}) => - MockFallbackStrategy(), - createQualitySelector: ( - {required VideoQuality videoQuality, - required FallbackStrategy fallbackStrategy}) => - MockQualitySelector(), - createCameraStateObserver: (_) => MockObserver(), - requestCameraPermissions: (_) => Future.value(), - startListeningForDeviceOrientationChange: (_, __) {}, - setPreviewSurfaceProvider: (_) => Future.value( - 3), // 3 is a random Flutter SurfaceTexture ID for testing}, - ); + camera.proxy = + getProxyForTestingResolutionPreset(mockProcessCameraProvider); - when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, any)) + when(mockProcessCameraProvider.bindToLifecycle(any, any)) .thenAnswer((_) async => mockCamera); when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) @@ -541,6 +617,189 @@ void main() { expect(camera.imageAnalysis!.resolutionSelector, isNull); }); + test( + 'createCamera properly sets filter for resolution preset for non-video capture use cases', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const CameraLensDirection testLensDirection = CameraLensDirection.front; + const int testSensorOrientation = 180; + const CameraDescription testCameraDescription = CameraDescription( + name: 'cameraName', + lensDirection: testLensDirection, + sensorOrientation: testSensorOrientation); + const bool enableAudio = true; + final MockCamera mockCamera = MockCamera(); + + // Mock/Detached objects for (typically attached) objects created by + // createCamera. + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Tell plugin to create mock/detached objects for testing createCamera + // as needed. + camera.proxy = + getProxyForTestingResolutionPreset(mockProcessCameraProvider); + + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + camera.processCameraProvider = mockProcessCameraProvider; + + // Test non-null resolution presets. + for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { + await camera.createCamera(testCameraDescription, resolutionPreset, + enableAudio: enableAudio); + + Size? expectedPreferredResolution; + switch (resolutionPreset) { + case ResolutionPreset.low: + expectedPreferredResolution = const Size(320, 240); + case ResolutionPreset.medium: + expectedPreferredResolution = const Size(720, 480); + case ResolutionPreset.high: + expectedPreferredResolution = const Size(1280, 720); + case ResolutionPreset.veryHigh: + expectedPreferredResolution = const Size(1920, 1080); + case ResolutionPreset.ultraHigh: + expectedPreferredResolution = const Size(3840, 2160); + case ResolutionPreset.max: + expectedPreferredResolution = null; + } + + if (expectedPreferredResolution == null) { + expect(camera.preview!.resolutionSelector!.resolutionFilter, isNull); + expect( + camera.imageCapture!.resolutionSelector!.resolutionFilter, isNull); + expect( + camera.imageAnalysis!.resolutionSelector!.resolutionFilter, isNull); + continue; + } + + expect( + camera.preview!.resolutionSelector!.resolutionFilter! + .preferredResolution, + equals(expectedPreferredResolution)); + expect( + camera + .imageCapture!.resolutionSelector!.resolutionStrategy!.boundSize, + equals(expectedPreferredResolution)); + expect( + camera + .imageAnalysis!.resolutionSelector!.resolutionStrategy!.boundSize, + equals(expectedPreferredResolution)); + } + + // Test null case. + await camera.createCamera(testCameraDescription, null); + expect(camera.preview!.resolutionSelector, isNull); + expect(camera.imageCapture!.resolutionSelector, isNull); + expect(camera.imageAnalysis!.resolutionSelector, isNull); + }); + + test( + 'createCamera properly sets aspect ratio based on preset resolution for non-video capture use cases', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const CameraLensDirection testLensDirection = CameraLensDirection.back; + const int testSensorOrientation = 90; + const CameraDescription testCameraDescription = CameraDescription( + name: 'cameraName', + lensDirection: testLensDirection, + sensorOrientation: testSensorOrientation); + const bool enableAudio = true; + final MockCamera mockCamera = MockCamera(); + + // Mock/Detached objects for (typically attached) objects created by + // createCamera. + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Tell plugin to create mock/detached objects for testing createCamera + // as needed. + camera.proxy = + getProxyForTestingResolutionPreset(mockProcessCameraProvider); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + camera.processCameraProvider = mockProcessCameraProvider; + + // Test non-null resolution presets. + for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { + await camera.createCamera(testCameraDescription, resolutionPreset, + enableAudio: enableAudio); + + int? expectedAspectRatio; + AspectRatioStrategy? expectedAspectRatioStrategy; + switch (resolutionPreset) { + case ResolutionPreset.low: + expectedAspectRatio = AspectRatio.ratio4To3; + case ResolutionPreset.high: + case ResolutionPreset.veryHigh: + case ResolutionPreset.ultraHigh: + expectedAspectRatio = AspectRatio.ratio16To9; + case ResolutionPreset.medium: + // Medium resolution preset uses aspect ratio 3:2 which is unsupported + // by CameraX. + case ResolutionPreset.max: + expectedAspectRatioStrategy = null; + } + + expectedAspectRatioStrategy = expectedAspectRatio == null + ? null + : AspectRatioStrategy.detached( + preferredAspectRatio: expectedAspectRatio, + fallbackRule: AspectRatioStrategy.fallbackRuleAuto); + + if (expectedAspectRatio == null) { + expect(camera.preview!.resolutionSelector!.aspectRatioStrategy, isNull); + expect(camera.imageCapture!.resolutionSelector!.aspectRatioStrategy, + isNull); + expect(camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy, + isNull); + continue; + } + + // Check aspect ratio. + expect( + camera.preview!.resolutionSelector!.aspectRatioStrategy! + .preferredAspectRatio, + equals(expectedAspectRatioStrategy!.preferredAspectRatio)); + expect( + camera.imageCapture!.resolutionSelector!.aspectRatioStrategy! + .preferredAspectRatio, + equals(expectedAspectRatioStrategy.preferredAspectRatio)); + expect( + camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy! + .preferredAspectRatio, + equals(expectedAspectRatioStrategy.preferredAspectRatio)); + + // Check fallback rule. + expect( + camera.preview!.resolutionSelector!.aspectRatioStrategy!.fallbackRule, + equals(expectedAspectRatioStrategy.fallbackRule)); + expect( + camera.imageCapture!.resolutionSelector!.aspectRatioStrategy! + .fallbackRule, + equals(expectedAspectRatioStrategy.fallbackRule)); + expect( + camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy! + .fallbackRule, + equals(expectedAspectRatioStrategy.fallbackRule)); + } + + // Test null case. + await camera.createCamera(testCameraDescription, null); + expect(camera.preview!.resolutionSelector, isNull); + expect(camera.imageCapture!.resolutionSelector, isNull); + expect(camera.imageAnalysis!.resolutionSelector, isNull); + }); + test( 'createCamera properly sets preset resolution for video capture use case', () async { @@ -559,58 +818,13 @@ void main() { final MockProcessCameraProvider mockProcessCameraProvider = MockProcessCameraProvider(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); - final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); - final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); - final MockPreview mockPreview = MockPreview(); - final MockImageCapture mockImageCapture = MockImageCapture(); - final MockVideoCapture mockVideoCapture = MockVideoCapture(); - final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); // Tell plugin to create mock/detached objects for testing createCamera // as needed. - camera.proxy = CameraXProxy( - getProcessCameraProvider: () => - Future.value(mockProcessCameraProvider), - createCameraSelector: (int cameraSelectorLensDirection) { - switch (cameraSelectorLensDirection) { - case CameraSelector.lensFacingFront: - return mockFrontCameraSelector; - case CameraSelector.lensFacingBack: - default: - return mockBackCameraSelector; - } - }, - createPreview: (_, __) => mockPreview, - createImageCapture: (_, __) => mockImageCapture, - createRecorder: (QualitySelector? qualitySelector) => - Recorder.detached(qualitySelector: qualitySelector), - createVideoCapture: (_) => Future.value(mockVideoCapture), - createImageAnalysis: (_, __) => mockImageAnalysis, - createResolutionStrategy: ( - {bool highestAvailable = false, - Size? boundSize, - int? fallbackRule}) => - MockResolutionStrategy(), - createResolutionSelector: (_) => MockResolutionSelector(), - createFallbackStrategy: ( - {required VideoQuality quality, - required VideoResolutionFallbackRule fallbackRule}) => - FallbackStrategy.detached( - quality: quality, fallbackRule: fallbackRule), - createQualitySelector: ( - {required VideoQuality videoQuality, - required FallbackStrategy fallbackStrategy}) => - QualitySelector.detached(qualityList: [ - VideoQualityData(quality: videoQuality) - ], fallbackStrategy: fallbackStrategy), - createCameraStateObserver: (void Function(Object) onChanged) => - Observer.detached(onChanged: onChanged), - requestCameraPermissions: (_) => Future.value(), - startListeningForDeviceOrientationChange: (_, __) {}, - ); + camera.proxy = + getProxyForTestingResolutionPreset(mockProcessCameraProvider); - when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, - [mockPreview, mockImageCapture, mockImageAnalysis])) + when(mockProcessCameraProvider.bindToLifecycle(any, any)) .thenAnswer((_) async => mockCamera); when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) @@ -719,7 +933,7 @@ void main() { Size? boundSize, int? fallbackRule}) => MockResolutionStrategy(), - createResolutionSelector: (_) => MockResolutionSelector(), + createResolutionSelector: (_, __, ___) => MockResolutionSelector(), createFallbackStrategy: ( {required VideoQuality quality, required VideoResolutionFallbackRule fallbackRule}) => @@ -732,21 +946,19 @@ void main() { Observer.detached(onChanged: onChanged), requestCameraPermissions: (_) => Future.value(), startListeningForDeviceOrientationChange: (_, __) {}, + createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), + createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), ); - // TODO(camsim99): Modify this when camera configuration is supported and - // default values no longer being used. - // https://github.com/flutter/flutter/issues/120468 - // https://github.com/flutter/flutter/issues/120467 final CameraInitializedEvent testCameraInitializedEvent = CameraInitializedEvent( cameraId, resolutionWidth.toDouble(), resolutionHeight.toDouble(), ExposureMode.auto, - false, + true, FocusMode.auto, - false); + true); // Call createCamera. when(mockPreview.setSurfaceProvider()).thenAnswer((_) async => cameraId); @@ -940,6 +1152,7 @@ void main() { MockProcessCameraProvider(); final MockCamera mockCamera = MockCamera(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCameraControl mockCameraControl = MockCameraControl(); final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); // Set directly for test versus calling createCamera. @@ -961,6 +1174,8 @@ void main() { when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => mockLiveCameraState); + when(mockCamera.getCameraControl()) + .thenAnswer((_) async => mockCameraControl); await camera.resumePreview(78); @@ -974,6 +1189,7 @@ void main() { as Observer), isTrue); expect(camera.cameraInfo, equals(mockCameraInfo)); + expect(camera.cameraControl, equals(mockCameraControl)); }); test( @@ -1017,6 +1233,7 @@ void main() { final MockCamera mockCamera = MockCamera(); final MockCamera newMockCamera = MockCamera(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCameraControl mockCameraControl = MockCameraControl(); final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); final MockLiveCameraState newMockLiveCameraState = MockLiveCameraState(); final TestSystemServicesHostApi mockSystemServicesApi = @@ -1055,6 +1272,8 @@ void main() { .thenAnswer((_) async => newMockCamera); when(newMockCamera.getCameraInfo()) .thenAnswer((_) async => mockCameraInfo); + when(newMockCamera.getCameraControl()) + .thenAnswer((_) async => mockCameraControl); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => newMockLiveCameraState); @@ -1066,6 +1285,7 @@ void main() { camera.cameraSelector!, [camera.videoCapture!])); expect(camera.camera, equals(newMockCamera)); expect(camera.cameraInfo, equals(mockCameraInfo)); + expect(camera.cameraControl, equals(mockCameraControl)); verify(mockLiveCameraState.removeObservers()); expect( await testCameraClosingObserver( @@ -1453,18 +1673,14 @@ void main() { () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 77; - final MockCameraControl mockCameraControl = MockCameraControl(); // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); - camera.camera = MockCamera(); + camera.cameraControl = MockCameraControl(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); - await camera.setFlashMode(cameraId, FlashMode.torch); await camera.takePicture(cameraId); verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff)); @@ -1479,14 +1695,11 @@ void main() { // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); - camera.camera = MockCamera(); + camera.cameraControl = mockCameraControl; // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); - for (final FlashMode flashMode in FlashMode.values) { await camera.setFlashMode(cameraId, flashMode); @@ -1520,10 +1733,7 @@ void main() { final MockCameraControl mockCameraControl = MockCameraControl(); // Set directly for test versus calling createCamera. - camera.camera = MockCamera(); - - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); + camera.cameraControl = mockCameraControl; await camera.setFlashMode(cameraId, FlashMode.torch); @@ -1538,10 +1748,7 @@ void main() { final MockCameraControl mockCameraControl = MockCameraControl(); // Set directly for test versus calling createCamera. - camera.camera = MockCamera(); - - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); + camera.cameraControl = mockCameraControl; for (final FlashMode flashMode in FlashMode.values) { camera.torchEnabled = true; @@ -1614,6 +1821,25 @@ void main() { expect(await camera.getExposureOffsetStepSize(55), 0.2); }); + test( + 'getExposureOffsetStepSize returns -1 when exposure compensation not supported on device', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 0, maxCompensation: 0), + exposureCompensationStep: 0); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + + expect(await camera.getExposureOffsetStepSize(55), -1); + }); + test('getMaxZoomLevel returns expected exposure offset', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); @@ -1657,10 +1883,7 @@ void main() { final MockCameraControl mockCameraControl = MockCameraControl(); // Set directly for test versus calling createCamera. - camera.camera = MockCamera(); - - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); + camera.cameraControl = mockCameraControl; await camera.setZoomLevel(cameraId, zoomRatio); @@ -1981,4 +2204,1272 @@ void main() { await camera.unlockCaptureOrientation(cameraId); expect(camera.captureOrientationLocked, isFalse); }); + + test('setExposureMode sets expected controlAeLock value via Camera2 interop', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraControl = mockCameraControl; + + // Tell plugin to create detached Camera2CameraControl and + // CaptureRequestOptions instances for testing. + camera.proxy = CameraXProxy( + getCamera2CameraControl: (CameraControl cameraControl) => + cameraControl == mockCameraControl + ? mockCamera2CameraControl + : Camera2CameraControl.detached(cameraControl: cameraControl), + createCaptureRequestOptions: + (List<(CaptureRequestKeySupportedType, Object?)> options) => + CaptureRequestOptions.detached(requestedOptions: options), + ); + + // Test auto mode. + await camera.setExposureMode(cameraId, ExposureMode.auto); + + VerificationResult verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + CaptureRequestOptions capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = + capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(false)); + + // Test locked mode. + clearInteractions(mockCamera2CameraControl); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + requestedOptions = capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(true)); + }); + + test( + 'setExposurePoint clears current auto-exposure metering point as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 93; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = mockCameraInfo; + + camera.proxy = getProxyForExposureAndFocus(); + + // Verify nothing happens if no current focus and metering action has been + // enabled. + await camera.setExposurePoint(cameraId, null); + verifyNever(mockCameraControl.startFocusAndMetering(any)); + verifyNever(mockCameraControl.cancelFocusAndMetering()); + + // Verify current auto-exposure metering point is removed if previously set. + final (MeteringPoint, int?) autofocusMeteringPointInfo = ( + MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAf + ); + List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAe + ), + autofocusMeteringPointInfo + ]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setExposurePoint(cameraId, null); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect( + capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); + + // Verify current focus and metering action is cleared if only previously + // set metering point was for auto-exposure. + meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAe + ) + ]; + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setExposurePoint(cameraId, null); + + verify(mockCameraControl.cancelFocusAndMetering()); + }); + + test('setExposurePoint throws CameraException if invalid point specified', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 23; + final MockCameraControl mockCameraControl = MockCameraControl(); + const Point invalidExposurePoint = Point(3, -1); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForExposureAndFocus(); + + expect(() => camera.setExposurePoint(cameraId, invalidExposurePoint), + throwsA(isA())); + }); + + test( + 'setExposurePoint adds new exposure point to focus metering action to start as expected when previous metering points have been set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 9; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = mockCameraInfo; + + camera.proxy = getProxyForExposureAndFocus(); + + // Verify current auto-exposure metering point is removed if previously set. + double exposurePointX = 0.8; + double exposurePointY = 0.1; + Point exposurePoint = Point(exposurePointX, exposurePointY); + final (MeteringPoint, int?) autofocusMeteringPointInfo = ( + MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAf + ); + List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAe + ), + autofocusMeteringPointInfo + ]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setExposurePoint(cameraId, exposurePoint); + + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + expect( + capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); + expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX)); + expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY)); + expect( + capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe)); + + // Verify exposure point is set when no auto-exposure metering point + // previously set, but an auto-focus point metering point has been. + exposurePointX = 0.2; + exposurePointY = 0.9; + exposurePoint = Point(exposurePointX, exposurePointY); + meteringPointInfos = <(MeteringPoint, int?)>[autofocusMeteringPointInfo]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setExposurePoint(cameraId, exposurePoint); + + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + capturedMeteringPointInfos = capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + expect( + capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); + expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX)); + expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY)); + expect( + capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe)); + }); + + test( + 'setExposurePoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 19; + final MockCameraControl mockCameraControl = MockCameraControl(); + const double exposurePointX = 0.8; + const double exposurePointY = 0.1; + const Point exposurePoint = + Point(exposurePointX, exposurePointY); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + camera.currentFocusMeteringAction = null; + + camera.proxy = getProxyForExposureAndFocus(); + + await camera.setExposurePoint(cameraId, exposurePoint); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY)); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAe)); + }); + + test( + 'setExposurePoint disables auto-cancel for focus and metering as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 2; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.1, 0.2); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Test not disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + await camera.setExposurePoint(cameraId, exposurePoint); + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + clearInteractions(mockCameraControl); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + await camera.setExposurePoint(cameraId, exposurePoint); + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setExposureOffset throws exception if exposure compensation not supported', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 6; + const double offset = 2; + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + + expect(() => camera.setExposureOffset(cameraId, offset), + throwsA(isA())); + }); + + test( + 'setExposureOffset throws exception if exposure compensation could not be set for unknown reason', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 11; + const double offset = 3; + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final CameraControl mockCameraControl = MockCameraControl(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.2); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + camera.cameraControl = mockCameraControl; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + when(mockCameraControl.setExposureCompensationIndex(15)).thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: + 'This is a test error message indicating exposure offset could not be set.')); + + expect(() => camera.setExposureOffset(cameraId, offset), + throwsA(isA())); + }); + + test( + 'setExposureOffset throws exception if exposure compensation could not be set due to camera being closed or newer value being set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 21; + const double offset = 5; + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final CameraControl mockCameraControl = MockCameraControl(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.1); + final int expectedExposureCompensationIndex = + (offset / exposureState.exposureCompensationStep).round(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + camera.cameraControl = mockCameraControl; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + when(mockCameraControl + .setExposureCompensationIndex(expectedExposureCompensationIndex)) + .thenAnswer((_) async => Future.value()); + + expect(() => camera.setExposureOffset(cameraId, offset), + throwsA(isA())); + }); + + test( + 'setExposureOffset behaves as expected to successful attempt to set exposure compensation index', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 11; + const double offset = 3; + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final CameraControl mockCameraControl = MockCameraControl(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.2); + final int expectedExposureCompensationIndex = + (offset / exposureState.exposureCompensationStep).round(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + camera.cameraControl = mockCameraControl; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + when(mockCameraControl + .setExposureCompensationIndex(expectedExposureCompensationIndex)) + .thenAnswer((_) async => Future.value( + (expectedExposureCompensationIndex * + exposureState.exposureCompensationStep) + .round())); + + // Exposure index * exposure offset step size = exposure offset, i.e. + // 15 * 0.2 = 3. + expect(await camera.setExposureOffset(cameraId, offset), equals(3)); + }); + + test('setFocusPoint clears current auto-exposure metering point as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 93; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = mockCameraInfo; + + camera.proxy = getProxyForExposureAndFocus(); + + // Verify nothing happens if no current focus and metering action has been + // enabled. + await camera.setFocusPoint(cameraId, null); + verifyNever(mockCameraControl.startFocusAndMetering(any)); + verifyNever(mockCameraControl.cancelFocusAndMetering()); + + // Verify current auto-exposure metering point is removed if previously set. + final (MeteringPoint, int?) autoexposureMeteringPointInfo = ( + MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAe + ); + List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAf + ), + autoexposureMeteringPointInfo + ]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setFocusPoint(cameraId, null); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first, + equals(autoexposureMeteringPointInfo)); + + // Verify current focus and metering action is cleared if only previously + // set metering point was for auto-exposure. + meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAf + ) + ]; + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setFocusPoint(cameraId, null); + + verify(mockCameraControl.cancelFocusAndMetering()); + }); + + test('setFocusPoint throws CameraException if invalid point specified', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 23; + final MockCameraControl mockCameraControl = MockCameraControl(); + const Point invalidFocusPoint = Point(-3, 1); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForExposureAndFocus(); + + expect(() => camera.setFocusPoint(cameraId, invalidFocusPoint), + throwsA(isA())); + }); + + test( + 'setFocusPoint adds new exposure point to focus metering action to start as expected when previous metering points have been set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 9; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = mockCameraInfo; + + camera.proxy = getProxyForExposureAndFocus(); + + // Verify current auto-exposure metering point is removed if previously set. + double focusPointX = 0.8; + double focusPointY = 0.1; + Point exposurePoint = Point(focusPointX, focusPointY); + final (MeteringPoint, int?) autoExposureMeteringPointInfo = ( + MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAe + ); + List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ + ( + MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), + FocusMeteringAction.flagAf + ), + autoExposureMeteringPointInfo + ]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setFocusPoint(cameraId, exposurePoint); + + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + expect(capturedMeteringPointInfos.first, + equals(autoExposureMeteringPointInfo)); + expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY)); + expect( + capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf)); + + // Verify exposure point is set when no auto-exposure metering point + // previously set, but an auto-focus point metering point has been. + focusPointX = 0.2; + focusPointY = 0.9; + exposurePoint = Point(focusPointX, focusPointY); + meteringPointInfos = <(MeteringPoint, int?)>[autoExposureMeteringPointInfo]; + + camera.currentFocusMeteringAction = + FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); + + await camera.setFocusPoint(cameraId, exposurePoint); + + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + capturedMeteringPointInfos = capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + expect(capturedMeteringPointInfos.first, + equals(autoExposureMeteringPointInfo)); + expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY)); + expect( + capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusPoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 19; + final MockCameraControl mockCameraControl = MockCameraControl(); + const double focusPointX = 0.8; + const double focusPointY = 0.1; + const Point exposurePoint = Point(focusPointX, focusPointY); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + camera.currentFocusMeteringAction = null; + + camera.proxy = getProxyForExposureAndFocus(); + + await camera.setFocusPoint(cameraId, exposurePoint); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test('setFocusPoint disables auto-cancel for focus and metering as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 2; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.1, 0.2); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Test not disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, exposurePoint); + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + clearInteractions(mockCameraControl); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, exposurePoint); + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setFocusMode does nothing if setting auto-focus mode and is already using auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 4; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set locked focus mode and then try to re-set it. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusMode(cameraId, FocusMode.locked); + verifyNoMoreInteractions(mockCameraControl); + }); + + test( + 'setFocusMode does nothing if setting locked focus mode and is already using locked focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 4; + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Camera uses auto-focus by default, so try setting auto mode again. + await camera.setFocusMode(cameraId, FocusMode.auto); + + verifyNoMoreInteractions(mockCameraControl); + }); + + test( + 'setFocusMode removes default auto-focus point if previously set and setting auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 5; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double exposurePointX = 0.2; + const double exposurePointY = 0.7; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure points. + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + + // Lock focus default focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + clearInteractions(mockCameraControl); + + // Test removal of default focus point. + await camera.setFocusMode(cameraId, FocusMode.auto); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + // We expect only the previously set exposure point to be re-set. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAe)); + }); + + test( + 'setFocusMode cancels focus and metering if only focus point previously set is a focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 5; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Lock focus default focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Test removal of default focus point. + await camera.setFocusMode(cameraId, FocusMode.auto); + + verify(mockCameraControl.cancelFocusAndMetering()); + }); + + test( + 'setFocusMode re-focuses on previously set auto-focus point with auto-canceled enabled if setting auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 6; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.1; + const double focusPointY = 0.2; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Lock a focus point. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + await camera.setFocusMode(cameraId, FocusMode.locked); + + clearInteractions(mockCameraControl); + + // Test re-focusing on previously set auto-focus point with auto-cancel enabled. + await camera.setFocusMode(cameraId, FocusMode.auto); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 7; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.88; + const double focusPointY = 0.33; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set a focus point. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect the set focus point to be locked. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point amongst others', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 8; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.38; + const double focusPointY = 0.38; + const double exposurePointX = 0.54; + const double exposurePointY = 0.45; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set focus and exposure points. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect two MeteringPoints, the set focus point and the set exposure + // point. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + + final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + expect(focusPoints.length, equals(1)); + expect(focusPoints.first.$1.x, equals(focusPointX)); + expect(focusPoints.first.$1.y, equals(focusPointY)); + expect(focusPoints.first.$1.size, isNull); + + final List<(MeteringPoint, int?)> exposurePoints = + capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAe) + .toList(); + expect(exposurePoints.length, equals(1)); + expect(exposurePoints.first.$1.x, equals(exposurePointX)); + expect(exposurePoints.first.$1.y, equals(exposurePointY)); + expect(exposurePoints.first.$1.size, isNull); + }); + + test( + 'setFocusMode starts expected focus and metering action if setting locked focus mode and current focus and metering action does not contain an auto-focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 9; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double exposurePointX = 0.8; + const double exposurePointY = 0.3; + const double defaultFocusPointX = 0.5; + const double defaultFocusPointY = 0.5; + const double defaultFocusPointSize = 1; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set an exposure point (creates a current focus and metering action + // without a focus point). + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect two MeteringPoints, the default focus point and the set + //exposure point. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + + final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + expect(focusPoints.length, equals(1)); + expect(focusPoints.first.$1.x, equals(defaultFocusPointX)); + expect(focusPoints.first.$1.y, equals(defaultFocusPointY)); + expect(focusPoints.first.$1.size, equals(defaultFocusPointSize)); + + final List<(MeteringPoint, int?)> exposurePoints = + capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAe) + .toList(); + expect(exposurePoints.length, equals(1)); + expect(exposurePoints.first.$1.x, equals(exposurePointX)); + expect(exposurePoints.first.$1.y, equals(exposurePointY)); + expect(exposurePoints.first.$1.size, isNull); + }); + + test( + 'setFocusMode starts expected focus and metering action if there is no current focus and metering action', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 10; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double defaultFocusPointX = 0.5; + const double defaultFocusPointY = 0.5; + const double defaultFocusPointSize = 1; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect only the default focus point to be set. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(defaultFocusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(defaultFocusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, + equals(defaultFocusPointSize)); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode re-sets exposure mode if setting locked focus mode while using auto exposure mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 11; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set auto exposure mode. + await camera.setExposureMode(cameraId, ExposureMode.auto); + clearInteractions(mockCamera2CameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + final CaptureRequestOptions capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + final List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = + capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(false)); + }); + + test( + 'setFocusPoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 22; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point focusPoint = Point(0.21, 0.21); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful to set locked focus + // mode. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point ( + // otherwise, focus and metering will be canceled altogether, which is + //considered a successful call). + await camera.setExposurePoint(cameraId, const Point(0.3, 0.4)); + + // Set locked focus mode so we can set auto mode (cannot set auto mode + // directly since it is the default). + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + // Make setting focus and metering action fail to test that auto-cancel is + // still disabled. + reset(mockFocusMeteringResult); + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, focusPoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setExposurePoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 342; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.23, 0.32); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful to set locked focus + // mode. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point ( + // otherwise, focus and metering will be canceled altogether, which is + //considered a successful call). + await camera.setExposurePoint(cameraId, const Point(0.4, 0.3)); + + // Set locked focus mode so we can set auto mode (cannot set auto mode + // directly since it is the default). + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + // Make setting focus and metering action fail to test that auto-cancel is + // still disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setExposurePoint(cameraId, exposurePoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setFocusPoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 232; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point focusPoint = Point(0.221, 0.211); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action fail to test auto-cancel is not + // disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point. + await camera.setExposurePoint(cameraId, const Point(0.43, 0.34)); + + // Test failing to set locked focus mode. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, focusPoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + }); + + test( + 'setExposurePoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 323; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.223, 0.332); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action fail to test auto-cancel is not + // disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point. + await camera.setExposurePoint(cameraId, const Point(0.5, 0.2)); + + // Test failing to set locked focus mode. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setExposurePoint(cameraId, exposurePoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + }); } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 4c90e940e71c..5d6d2051212d 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -3,47 +3,53 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:typed_data' as _i29; +import 'dart:async' as _i17; +import 'dart:typed_data' as _i33; +import 'dart:ui' as _i11; -import 'package:camera_android_camerax/src/analyzer.dart' as _i15; +import 'package:camera_android_camerax/src/analyzer.dart' as _i16; +import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i19; import 'package:camera_android_camerax/src/camera.dart' as _i9; +import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i24; import 'package:camera_android_camerax/src/camera_control.dart' as _i3; import 'package:camera_android_camerax/src/camera_info.dart' as _i2; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i22; -import 'package:camera_android_camerax/src/camera_state.dart' as _i18; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i26; +import 'package:camera_android_camerax/src/camera_state.dart' as _i20; import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/capture_request_options.dart' + as _i25; import 'package:camera_android_camerax/src/exposure_state.dart' as _i5; -import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i23; -import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i21; -import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i20; -import 'package:camera_android_camerax/src/image_analysis.dart' as _i24; -import 'package:camera_android_camerax/src/image_capture.dart' as _i25; -import 'package:camera_android_camerax/src/image_proxy.dart' as _i17; +import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i27; +import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i23; +import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i22; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i28; +import 'package:camera_android_camerax/src/image_capture.dart' as _i29; +import 'package:camera_android_camerax/src/image_proxy.dart' as _i18; import 'package:camera_android_camerax/src/live_data.dart' as _i4; -import 'package:camera_android_camerax/src/observer.dart' as _i28; +import 'package:camera_android_camerax/src/observer.dart' as _i32; import 'package:camera_android_camerax/src/pending_recording.dart' as _i10; -import 'package:camera_android_camerax/src/plane_proxy.dart' as _i27; -import 'package:camera_android_camerax/src/preview.dart' as _i30; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i31; +import 'package:camera_android_camerax/src/preview.dart' as _i34; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i31; -import 'package:camera_android_camerax/src/quality_selector.dart' as _i33; -import 'package:camera_android_camerax/src/recorder.dart' as _i11; + as _i35; +import 'package:camera_android_camerax/src/quality_selector.dart' as _i37; +import 'package:camera_android_camerax/src/recorder.dart' as _i12; import 'package:camera_android_camerax/src/recording.dart' as _i8; -import 'package:camera_android_camerax/src/resolution_selector.dart' as _i34; -import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i35; -import 'package:camera_android_camerax/src/use_case.dart' as _i32; -import 'package:camera_android_camerax/src/video_capture.dart' as _i36; -import 'package:camera_android_camerax/src/zoom_state.dart' as _i19; +import 'package:camera_android_camerax/src/resolution_filter.dart' as _i38; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i39; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i40; +import 'package:camera_android_camerax/src/use_case.dart' as _i36; +import 'package:camera_android_camerax/src/video_capture.dart' as _i41; +import 'package:camera_android_camerax/src/zoom_state.dart' as _i21; import 'package:camera_platform_interface/camera_platform_interface.dart' as _i6; -import 'package:flutter/foundation.dart' as _i14; -import 'package:flutter/services.dart' as _i13; -import 'package:flutter/widgets.dart' as _i12; +import 'package:flutter/foundation.dart' as _i15; +import 'package:flutter/services.dart' as _i14; +import 'package:flutter/widgets.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i26; +import 'package:mockito/src/dummies.dart' as _i30; -import 'test_camerax_library.g.dart' as _i37; +import 'test_camerax_library.g.dart' as _i42; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -163,8 +169,8 @@ class _FakePendingRecording_9 extends _i1.SmartFake ); } -class _FakeRecorder_10 extends _i1.SmartFake implements _i11.Recorder { - _FakeRecorder_10( +class _FakeSize_10 extends _i1.SmartFake implements _i11.Size { + _FakeSize_10( Object parent, Invocation parentInvocation, ) : super( @@ -173,8 +179,18 @@ class _FakeRecorder_10 extends _i1.SmartFake implements _i11.Recorder { ); } -class _FakeWidget_11 extends _i1.SmartFake implements _i12.Widget { - _FakeWidget_11( +class _FakeRecorder_11 extends _i1.SmartFake implements _i12.Recorder { + _FakeRecorder_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWidget_12 extends _i1.SmartFake implements _i13.Widget { + _FakeWidget_12( Object parent, Invocation parentInvocation, ) : super( @@ -184,13 +200,13 @@ class _FakeWidget_11 extends _i1.SmartFake implements _i12.Widget { @override String toString( - {_i13.DiagnosticLevel? minLevel = _i13.DiagnosticLevel.info}) => + {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => super.toString(); } -class _FakeInheritedWidget_12 extends _i1.SmartFake - implements _i12.InheritedWidget { - _FakeInheritedWidget_12( +class _FakeInheritedWidget_13 extends _i1.SmartFake + implements _i13.InheritedWidget { + _FakeInheritedWidget_13( Object parent, Invocation parentInvocation, ) : super( @@ -200,13 +216,13 @@ class _FakeInheritedWidget_12 extends _i1.SmartFake @override String toString( - {_i13.DiagnosticLevel? minLevel = _i13.DiagnosticLevel.info}) => + {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => super.toString(); } -class _FakeDiagnosticsNode_13 extends _i1.SmartFake - implements _i14.DiagnosticsNode { - _FakeDiagnosticsNode_13( +class _FakeDiagnosticsNode_14 extends _i1.SmartFake + implements _i15.DiagnosticsNode { + _FakeDiagnosticsNode_14( Object parent, Invocation parentInvocation, ) : super( @@ -216,8 +232,8 @@ class _FakeDiagnosticsNode_13 extends _i1.SmartFake @override String toString({ - _i14.TextTreeConfiguration? parentConfiguration, - _i13.DiagnosticLevel? minLevel = _i13.DiagnosticLevel.info, + _i15.TextTreeConfiguration? parentConfiguration, + _i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info, }) => super.toString(); } @@ -226,15 +242,36 @@ class _FakeDiagnosticsNode_13 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockAnalyzer extends _i1.Mock implements _i15.Analyzer { +class MockAnalyzer extends _i1.Mock implements _i16.Analyzer { @override - _i16.Future Function(_i17.ImageProxy) get analyze => + _i17.Future Function(_i18.ImageProxy) get analyze => (super.noSuchMethod( Invocation.getter(#analyze), - returnValue: (_i17.ImageProxy imageProxy) => _i16.Future.value(), - returnValueForMissingStub: (_i17.ImageProxy imageProxy) => - _i16.Future.value(), - ) as _i16.Future Function(_i17.ImageProxy)); + returnValue: (_i18.ImageProxy imageProxy) => _i17.Future.value(), + returnValueForMissingStub: (_i18.ImageProxy imageProxy) => + _i17.Future.value(), + ) as _i17.Future Function(_i18.ImageProxy)); +} + +/// A class which mocks [AspectRatioStrategy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockAspectRatioStrategy extends _i1.Mock + implements _i19.AspectRatioStrategy { + @override + int get preferredAspectRatio => (super.noSuchMethod( + Invocation.getter(#preferredAspectRatio), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + int get fallbackRule => (super.noSuchMethod( + Invocation.getter(#fallbackRule), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); } /// A class which mocks [Camera]. @@ -243,12 +280,12 @@ class MockAnalyzer extends _i1.Mock implements _i15.Analyzer { // ignore: must_be_immutable class MockCamera extends _i1.Mock implements _i9.Camera { @override - _i16.Future<_i2.CameraInfo> getCameraInfo() => (super.noSuchMethod( + _i17.Future<_i2.CameraInfo> getCameraInfo() => (super.noSuchMethod( Invocation.method( #getCameraInfo, [], ), - returnValue: _i16.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + returnValue: _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( this, Invocation.method( #getCameraInfo, @@ -256,22 +293,22 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i16.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( this, Invocation.method( #getCameraInfo, [], ), )), - ) as _i16.Future<_i2.CameraInfo>); + ) as _i17.Future<_i2.CameraInfo>); @override - _i16.Future<_i3.CameraControl> getCameraControl() => (super.noSuchMethod( + _i17.Future<_i3.CameraControl> getCameraControl() => (super.noSuchMethod( Invocation.method( #getCameraControl, [], ), - returnValue: _i16.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + returnValue: _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( this, Invocation.method( #getCameraControl, @@ -279,14 +316,14 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i16.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( this, Invocation.method( #getCameraControl, [], ), )), - ) as _i16.Future<_i3.CameraControl>); + ) as _i17.Future<_i3.CameraControl>); } /// A class which mocks [CameraInfo]. @@ -295,24 +332,24 @@ class MockCamera extends _i1.Mock implements _i9.Camera { // ignore: must_be_immutable class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { @override - _i16.Future getSensorRotationDegrees() => (super.noSuchMethod( + _i17.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( #getSensorRotationDegrees, [], ), - returnValue: _i16.Future.value(0), - returnValueForMissingStub: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i17.Future.value(0), + returnValueForMissingStub: _i17.Future.value(0), + ) as _i17.Future); @override - _i16.Future<_i4.LiveData<_i18.CameraState>> getCameraState() => + _i17.Future<_i4.LiveData<_i20.CameraState>> getCameraState() => (super.noSuchMethod( Invocation.method( #getCameraState, [], ), - returnValue: _i16.Future<_i4.LiveData<_i18.CameraState>>.value( - _FakeLiveData_2<_i18.CameraState>( + returnValue: _i17.Future<_i4.LiveData<_i20.CameraState>>.value( + _FakeLiveData_2<_i20.CameraState>( this, Invocation.method( #getCameraState, @@ -320,23 +357,23 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i16.Future<_i4.LiveData<_i18.CameraState>>.value( - _FakeLiveData_2<_i18.CameraState>( + _i17.Future<_i4.LiveData<_i20.CameraState>>.value( + _FakeLiveData_2<_i20.CameraState>( this, Invocation.method( #getCameraState, [], ), )), - ) as _i16.Future<_i4.LiveData<_i18.CameraState>>); + ) as _i17.Future<_i4.LiveData<_i20.CameraState>>); @override - _i16.Future<_i5.ExposureState> getExposureState() => (super.noSuchMethod( + _i17.Future<_i5.ExposureState> getExposureState() => (super.noSuchMethod( Invocation.method( #getExposureState, [], ), - returnValue: _i16.Future<_i5.ExposureState>.value(_FakeExposureState_3( + returnValue: _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( this, Invocation.method( #getExposureState, @@ -344,24 +381,24 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i16.Future<_i5.ExposureState>.value(_FakeExposureState_3( + _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( this, Invocation.method( #getExposureState, [], ), )), - ) as _i16.Future<_i5.ExposureState>); + ) as _i17.Future<_i5.ExposureState>); @override - _i16.Future<_i4.LiveData<_i19.ZoomState>> getZoomState() => + _i17.Future<_i4.LiveData<_i21.ZoomState>> getZoomState() => (super.noSuchMethod( Invocation.method( #getZoomState, [], ), - returnValue: _i16.Future<_i4.LiveData<_i19.ZoomState>>.value( - _FakeLiveData_2<_i19.ZoomState>( + returnValue: _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_2<_i21.ZoomState>( this, Invocation.method( #getZoomState, @@ -369,15 +406,15 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i16.Future<_i4.LiveData<_i19.ZoomState>>.value( - _FakeLiveData_2<_i19.ZoomState>( + _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_2<_i21.ZoomState>( this, Invocation.method( #getZoomState, [], ), )), - ) as _i16.Future<_i4.LiveData<_i19.ZoomState>>); + ) as _i17.Future<_i4.LiveData<_i21.ZoomState>>); } /// A class which mocks [CameraControl]. @@ -386,58 +423,90 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { // ignore: must_be_immutable class MockCameraControl extends _i1.Mock implements _i3.CameraControl { @override - _i16.Future enableTorch(bool? torch) => (super.noSuchMethod( + _i17.Future enableTorch(bool? torch) => (super.noSuchMethod( Invocation.method( #enableTorch, [torch], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future setZoomRatio(double? ratio) => (super.noSuchMethod( + _i17.Future setZoomRatio(double? ratio) => (super.noSuchMethod( Invocation.method( #setZoomRatio, [ratio], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future<_i20.FocusMeteringResult?> startFocusAndMetering( - _i21.FocusMeteringAction? action) => + _i17.Future<_i22.FocusMeteringResult?> startFocusAndMetering( + _i23.FocusMeteringAction? action) => (super.noSuchMethod( Invocation.method( #startFocusAndMetering, [action], ), - returnValue: _i16.Future<_i20.FocusMeteringResult?>.value(), + returnValue: _i17.Future<_i22.FocusMeteringResult?>.value(), returnValueForMissingStub: - _i16.Future<_i20.FocusMeteringResult?>.value(), - ) as _i16.Future<_i20.FocusMeteringResult?>); + _i17.Future<_i22.FocusMeteringResult?>.value(), + ) as _i17.Future<_i22.FocusMeteringResult?>); @override - _i16.Future cancelFocusAndMetering() => (super.noSuchMethod( + _i17.Future cancelFocusAndMetering() => (super.noSuchMethod( Invocation.method( #cancelFocusAndMetering, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future setExposureCompensationIndex(int? index) => + _i17.Future setExposureCompensationIndex(int? index) => (super.noSuchMethod( Invocation.method( #setExposureCompensationIndex, [index], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [Camera2CameraControl]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCamera2CameraControl extends _i1.Mock + implements _i24.Camera2CameraControl { + @override + _i3.CameraControl get cameraControl => (super.noSuchMethod( + Invocation.getter(#cameraControl), + returnValue: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + returnValueForMissingStub: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + ) as _i3.CameraControl); + + @override + _i17.Future addCaptureRequestOptions( + _i25.CaptureRequestOptions? captureRequestOptions) => + (super.noSuchMethod( + Invocation.method( + #addCaptureRequestOptions, + [captureRequestOptions], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } /// A class which mocks [CameraImageData]. @@ -484,19 +553,19 @@ class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraSelector extends _i1.Mock implements _i22.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i26.CameraSelector { @override - _i16.Future> filter(List<_i2.CameraInfo>? cameraInfos) => + _i17.Future> filter(List<_i2.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), returnValue: - _i16.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i2.CameraInfo>[]), returnValueForMissingStub: - _i16.Future>.value(<_i2.CameraInfo>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i2.CameraInfo>[]), + ) as _i17.Future>); } /// A class which mocks [ExposureState]. @@ -530,7 +599,7 @@ class MockExposureState extends _i1.Mock implements _i5.ExposureState { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockFallbackStrategy extends _i1.Mock implements _i23.FallbackStrategy { +class MockFallbackStrategy extends _i1.Mock implements _i27.FallbackStrategy { @override _i7.VideoQuality get quality => (super.noSuchMethod( Invocation.getter(#quality), @@ -547,74 +616,91 @@ class MockFallbackStrategy extends _i1.Mock implements _i23.FallbackStrategy { ) as _i7.VideoResolutionFallbackRule); } +/// A class which mocks [FocusMeteringResult]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockFocusMeteringResult extends _i1.Mock + implements _i22.FocusMeteringResult { + @override + _i17.Future isFocusSuccessful() => (super.noSuchMethod( + Invocation.method( + #isFocusSuccessful, + [], + ), + returnValue: _i17.Future.value(false), + returnValueForMissingStub: _i17.Future.value(false), + ) as _i17.Future); +} + /// A class which mocks [ImageAnalysis]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageAnalysis extends _i1.Mock implements _i24.ImageAnalysis { +class MockImageAnalysis extends _i1.Mock implements _i28.ImageAnalysis { @override - _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( #setTargetRotation, [rotation], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future setAnalyzer(_i15.Analyzer? analyzer) => (super.noSuchMethod( + _i17.Future setAnalyzer(_i16.Analyzer? analyzer) => (super.noSuchMethod( Invocation.method( #setAnalyzer, [analyzer], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future clearAnalyzer() => (super.noSuchMethod( + _i17.Future clearAnalyzer() => (super.noSuchMethod( Invocation.method( #clearAnalyzer, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } /// A class which mocks [ImageCapture]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageCapture extends _i1.Mock implements _i25.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { @override - _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( #setTargetRotation, [rotation], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( + _i17.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( Invocation.method( #setFlashMode, [newFlashMode], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future takePicture() => (super.noSuchMethod( + _i17.Future takePicture() => (super.noSuchMethod( Invocation.method( #takePicture, [], ), - returnValue: _i16.Future.value(_i26.dummyValue( + returnValue: _i17.Future.value(_i30.dummyValue( this, Invocation.method( #takePicture, @@ -622,21 +708,21 @@ class MockImageCapture extends _i1.Mock implements _i25.ImageCapture { ), )), returnValueForMissingStub: - _i16.Future.value(_i26.dummyValue( + _i17.Future.value(_i30.dummyValue( this, Invocation.method( #takePicture, [], ), )), - ) as _i16.Future); + ) as _i17.Future); } /// A class which mocks [ImageProxy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageProxy extends _i1.Mock implements _i17.ImageProxy { +class MockImageProxy extends _i1.Mock implements _i18.ImageProxy { @override int get format => (super.noSuchMethod( Invocation.getter(#format), @@ -659,33 +745,33 @@ class MockImageProxy extends _i1.Mock implements _i17.ImageProxy { ) as int); @override - _i16.Future> getPlanes() => (super.noSuchMethod( + _i17.Future> getPlanes() => (super.noSuchMethod( Invocation.method( #getPlanes, [], ), returnValue: - _i16.Future>.value(<_i27.PlaneProxy>[]), + _i17.Future>.value(<_i31.PlaneProxy>[]), returnValueForMissingStub: - _i16.Future>.value(<_i27.PlaneProxy>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i31.PlaneProxy>[]), + ) as _i17.Future>); @override - _i16.Future close() => (super.noSuchMethod( + _i17.Future close() => (super.noSuchMethod( Invocation.method( #close, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } /// A class which mocks [Observer]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockObserver extends _i1.Mock implements _i28.Observer<_i18.CameraState> { +class MockObserver extends _i1.Mock implements _i32.Observer<_i20.CameraState> { @override void Function(Object) get onChanged => (super.noSuchMethod( Invocation.getter(#onChanged), @@ -709,12 +795,12 @@ class MockObserver extends _i1.Mock implements _i28.Observer<_i18.CameraState> { // ignore: must_be_immutable class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { @override - _i16.Future<_i8.Recording> start() => (super.noSuchMethod( + _i17.Future<_i8.Recording> start() => (super.noSuchMethod( Invocation.method( #start, [], ), - returnValue: _i16.Future<_i8.Recording>.value(_FakeRecording_6( + returnValue: _i17.Future<_i8.Recording>.value(_FakeRecording_6( this, Invocation.method( #start, @@ -722,27 +808,27 @@ class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { ), )), returnValueForMissingStub: - _i16.Future<_i8.Recording>.value(_FakeRecording_6( + _i17.Future<_i8.Recording>.value(_FakeRecording_6( this, Invocation.method( #start, [], ), )), - ) as _i16.Future<_i8.Recording>); + ) as _i17.Future<_i8.Recording>); } /// A class which mocks [PlaneProxy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPlaneProxy extends _i1.Mock implements _i27.PlaneProxy { +class MockPlaneProxy extends _i1.Mock implements _i31.PlaneProxy { @override - _i29.Uint8List get buffer => (super.noSuchMethod( + _i33.Uint8List get buffer => (super.noSuchMethod( Invocation.getter(#buffer), - returnValue: _i29.Uint8List(0), - returnValueForMissingStub: _i29.Uint8List(0), - ) as _i29.Uint8List); + returnValue: _i33.Uint8List(0), + returnValueForMissingStub: _i33.Uint8List(0), + ) as _i33.Uint8List); @override int get pixelStride => (super.noSuchMethod( @@ -763,26 +849,26 @@ class MockPlaneProxy extends _i1.Mock implements _i27.PlaneProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPreview extends _i1.Mock implements _i30.Preview { +class MockPreview extends _i1.Mock implements _i34.Preview { @override - _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( #setTargetRotation, [rotation], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future setSurfaceProvider() => (super.noSuchMethod( + _i17.Future setSurfaceProvider() => (super.noSuchMethod( Invocation.method( #setSurfaceProvider, [], ), - returnValue: _i16.Future.value(0), - returnValueForMissingStub: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i17.Future.value(0), + returnValueForMissingStub: _i17.Future.value(0), + ) as _i17.Future); @override void releaseFlutterSurfaceTexture() => super.noSuchMethod( @@ -794,13 +880,13 @@ class MockPreview extends _i1.Mock implements _i30.Preview { ); @override - _i16.Future<_i7.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + _i17.Future<_i7.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), returnValue: - _i16.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( this, Invocation.method( #getResolutionInfo, @@ -808,14 +894,14 @@ class MockPreview extends _i1.Mock implements _i30.Preview { ), )), returnValueForMissingStub: - _i16.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( this, Invocation.method( #getResolutionInfo, [], ), )), - ) as _i16.Future<_i7.ResolutionInfo>); + ) as _i17.Future<_i7.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. @@ -823,24 +909,24 @@ class MockPreview extends _i1.Mock implements _i30.Preview { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockProcessCameraProvider extends _i1.Mock - implements _i31.ProcessCameraProvider { + implements _i35.ProcessCameraProvider { @override - _i16.Future> getAvailableCameraInfos() => + _i17.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), returnValue: - _i16.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i2.CameraInfo>[]), returnValueForMissingStub: - _i16.Future>.value(<_i2.CameraInfo>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i2.CameraInfo>[]), + ) as _i17.Future>); @override - _i16.Future<_i9.Camera> bindToLifecycle( - _i22.CameraSelector? cameraSelector, - List<_i32.UseCase>? useCases, + _i17.Future<_i9.Camera> bindToLifecycle( + _i26.CameraSelector? cameraSelector, + List<_i36.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -850,7 +936,7 @@ class MockProcessCameraProvider extends _i1.Mock useCases, ], ), - returnValue: _i16.Future<_i9.Camera>.value(_FakeCamera_8( + returnValue: _i17.Future<_i9.Camera>.value(_FakeCamera_8( this, Invocation.method( #bindToLifecycle, @@ -860,7 +946,7 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - returnValueForMissingStub: _i16.Future<_i9.Camera>.value(_FakeCamera_8( + returnValueForMissingStub: _i17.Future<_i9.Camera>.value(_FakeCamera_8( this, Invocation.method( #bindToLifecycle, @@ -870,20 +956,20 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - ) as _i16.Future<_i9.Camera>); + ) as _i17.Future<_i9.Camera>); @override - _i16.Future isBound(_i32.UseCase? useCase) => (super.noSuchMethod( + _i17.Future isBound(_i36.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], ), - returnValue: _i16.Future.value(false), - returnValueForMissingStub: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i17.Future.value(false), + returnValueForMissingStub: _i17.Future.value(false), + ) as _i17.Future); @override - void unbind(List<_i32.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i36.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -905,7 +991,7 @@ class MockProcessCameraProvider extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockQualitySelector extends _i1.Mock implements _i33.QualitySelector { +class MockQualitySelector extends _i1.Mock implements _i37.QualitySelector { @override List<_i7.VideoQualityData> get qualityList => (super.noSuchMethod( Invocation.getter(#qualityList), @@ -918,16 +1004,16 @@ class MockQualitySelector extends _i1.Mock implements _i33.QualitySelector { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockRecorder extends _i1.Mock implements _i11.Recorder { +class MockRecorder extends _i1.Mock implements _i12.Recorder { @override - _i16.Future<_i10.PendingRecording> prepareRecording(String? path) => + _i17.Future<_i10.PendingRecording> prepareRecording(String? path) => (super.noSuchMethod( Invocation.method( #prepareRecording, [path], ), returnValue: - _i16.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( this, Invocation.method( #prepareRecording, @@ -935,14 +1021,33 @@ class MockRecorder extends _i1.Mock implements _i11.Recorder { ), )), returnValueForMissingStub: - _i16.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( this, Invocation.method( #prepareRecording, [path], ), )), - ) as _i16.Future<_i10.PendingRecording>); + ) as _i17.Future<_i10.PendingRecording>); +} + +/// A class which mocks [ResolutionFilter]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionFilter extends _i1.Mock implements _i38.ResolutionFilter { + @override + _i11.Size get preferredResolution => (super.noSuchMethod( + Invocation.getter(#preferredResolution), + returnValue: _FakeSize_10( + this, + Invocation.getter(#preferredResolution), + ), + returnValueForMissingStub: _FakeSize_10( + this, + Invocation.getter(#preferredResolution), + ), + ) as _i11.Size); } /// A class which mocks [ResolutionSelector]. @@ -950,14 +1055,14 @@ class MockRecorder extends _i1.Mock implements _i11.Recorder { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionSelector extends _i1.Mock - implements _i34.ResolutionSelector {} + implements _i39.ResolutionSelector {} /// A class which mocks [ResolutionStrategy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionStrategy extends _i1.Mock - implements _i35.ResolutionStrategy {} + implements _i40.ResolutionStrategy {} /// A class which mocks [Recording]. /// @@ -965,68 +1070,68 @@ class MockResolutionStrategy extends _i1.Mock // ignore: must_be_immutable class MockRecording extends _i1.Mock implements _i8.Recording { @override - _i16.Future close() => (super.noSuchMethod( + _i17.Future close() => (super.noSuchMethod( Invocation.method( #close, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future pause() => (super.noSuchMethod( + _i17.Future pause() => (super.noSuchMethod( Invocation.method( #pause, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future resume() => (super.noSuchMethod( + _i17.Future resume() => (super.noSuchMethod( Invocation.method( #resume, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future stop() => (super.noSuchMethod( + _i17.Future stop() => (super.noSuchMethod( Invocation.method( #stop, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } /// A class which mocks [VideoCapture]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockVideoCapture extends _i1.Mock implements _i36.VideoCapture { +class MockVideoCapture extends _i1.Mock implements _i41.VideoCapture { @override - _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( #setTargetRotation, [rotation], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future<_i11.Recorder> getOutput() => (super.noSuchMethod( + _i17.Future<_i12.Recorder> getOutput() => (super.noSuchMethod( Invocation.method( #getOutput, [], ), - returnValue: _i16.Future<_i11.Recorder>.value(_FakeRecorder_10( + returnValue: _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( this, Invocation.method( #getOutput, @@ -1034,32 +1139,32 @@ class MockVideoCapture extends _i1.Mock implements _i36.VideoCapture { ), )), returnValueForMissingStub: - _i16.Future<_i11.Recorder>.value(_FakeRecorder_10( + _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( this, Invocation.method( #getOutput, [], ), )), - ) as _i16.Future<_i11.Recorder>); + ) as _i17.Future<_i12.Recorder>); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i12.BuildContext { +class MockBuildContext extends _i1.Mock implements _i13.BuildContext { @override - _i12.Widget get widget => (super.noSuchMethod( + _i13.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), - returnValue: _FakeWidget_11( + returnValue: _FakeWidget_12( this, Invocation.getter(#widget), ), - returnValueForMissingStub: _FakeWidget_11( + returnValueForMissingStub: _FakeWidget_12( this, Invocation.getter(#widget), ), - ) as _i12.Widget); + ) as _i13.Widget); @override bool get mounted => (super.noSuchMethod( @@ -1076,8 +1181,8 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { ) as bool); @override - _i12.InheritedWidget dependOnInheritedElement( - _i12.InheritedElement? ancestor, { + _i13.InheritedWidget dependOnInheritedElement( + _i13.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( @@ -1086,7 +1191,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { [ancestor], {#aspect: aspect}, ), - returnValue: _FakeInheritedWidget_12( + returnValue: _FakeInheritedWidget_13( this, Invocation.method( #dependOnInheritedElement, @@ -1094,7 +1199,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#aspect: aspect}, ), ), - returnValueForMissingStub: _FakeInheritedWidget_12( + returnValueForMissingStub: _FakeInheritedWidget_13( this, Invocation.method( #dependOnInheritedElement, @@ -1102,10 +1207,10 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#aspect: aspect}, ), ), - ) as _i12.InheritedWidget); + ) as _i13.InheritedWidget); @override - void visitAncestorElements(_i12.ConditionalElementVisitor? visitor) => + void visitAncestorElements(_i13.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, @@ -1115,7 +1220,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { ); @override - void visitChildElements(_i12.ElementVisitor? visitor) => super.noSuchMethod( + void visitChildElements(_i13.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], @@ -1124,7 +1229,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { ); @override - void dispatchNotification(_i12.Notification? notification) => + void dispatchNotification(_i13.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, @@ -1134,9 +1239,9 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { ); @override - _i14.DiagnosticsNode describeElement( + _i15.DiagnosticsNode describeElement( String? name, { - _i14.DiagnosticsTreeStyle? style = _i14.DiagnosticsTreeStyle.errorProperty, + _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -1144,7 +1249,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_13( + returnValue: _FakeDiagnosticsNode_14( this, Invocation.method( #describeElement, @@ -1152,7 +1257,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#style: style}, ), ), - returnValueForMissingStub: _FakeDiagnosticsNode_13( + returnValueForMissingStub: _FakeDiagnosticsNode_14( this, Invocation.method( #describeElement, @@ -1160,12 +1265,12 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#style: style}, ), ), - ) as _i14.DiagnosticsNode); + ) as _i15.DiagnosticsNode); @override - _i14.DiagnosticsNode describeWidget( + _i15.DiagnosticsNode describeWidget( String? name, { - _i14.DiagnosticsTreeStyle? style = _i14.DiagnosticsTreeStyle.errorProperty, + _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -1173,7 +1278,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_13( + returnValue: _FakeDiagnosticsNode_14( this, Invocation.method( #describeWidget, @@ -1181,7 +1286,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#style: style}, ), ), - returnValueForMissingStub: _FakeDiagnosticsNode_13( + returnValueForMissingStub: _FakeDiagnosticsNode_14( this, Invocation.method( #describeWidget, @@ -1189,10 +1294,10 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { {#style: style}, ), ), - ) as _i14.DiagnosticsNode); + ) as _i15.DiagnosticsNode); @override - List<_i14.DiagnosticsNode> describeMissingAncestor( + List<_i15.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( @@ -1200,39 +1305,39 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { [], {#expectedAncestorType: expectedAncestorType}, ), - returnValue: <_i14.DiagnosticsNode>[], - returnValueForMissingStub: <_i14.DiagnosticsNode>[], - ) as List<_i14.DiagnosticsNode>); + returnValue: <_i15.DiagnosticsNode>[], + returnValueForMissingStub: <_i15.DiagnosticsNode>[], + ) as List<_i15.DiagnosticsNode>); @override - _i14.DiagnosticsNode describeOwnershipChain(String? name) => + _i15.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), - returnValue: _FakeDiagnosticsNode_13( + returnValue: _FakeDiagnosticsNode_14( this, Invocation.method( #describeOwnershipChain, [name], ), ), - returnValueForMissingStub: _FakeDiagnosticsNode_13( + returnValueForMissingStub: _FakeDiagnosticsNode_14( this, Invocation.method( #describeOwnershipChain, [name], ), ), - ) as _i14.DiagnosticsNode); + ) as _i15.DiagnosticsNode); } /// A class which mocks [TestInstanceManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i37.TestInstanceManagerHostApi { + implements _i42.TestInstanceManagerHostApi { @override void clear() => super.noSuchMethod( Invocation.method( @@ -1247,19 +1352,19 @@ class MockTestInstanceManagerHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestSystemServicesHostApi extends _i1.Mock - implements _i37.TestSystemServicesHostApi { + implements _i42.TestSystemServicesHostApi { @override - _i16.Future<_i7.CameraPermissionsErrorData?> requestCameraPermissions( + _i17.Future<_i7.CameraPermissionsErrorData?> requestCameraPermissions( bool? enableAudio) => (super.noSuchMethod( Invocation.method( #requestCameraPermissions, [enableAudio], ), - returnValue: _i16.Future<_i7.CameraPermissionsErrorData?>.value(), + returnValue: _i17.Future<_i7.CameraPermissionsErrorData?>.value(), returnValueForMissingStub: - _i16.Future<_i7.CameraPermissionsErrorData?>.value(), - ) as _i16.Future<_i7.CameraPermissionsErrorData?>); + _i17.Future<_i7.CameraPermissionsErrorData?>.value(), + ) as _i17.Future<_i7.CameraPermissionsErrorData?>); @override String getTempFilePath( @@ -1274,7 +1379,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock suffix, ], ), - returnValue: _i26.dummyValue( + returnValue: _i30.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1284,7 +1389,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock ], ), ), - returnValueForMissingStub: _i26.dummyValue( + returnValueForMissingStub: _i30.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1301,7 +1406,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockZoomState extends _i1.Mock implements _i19.ZoomState { +class MockZoomState extends _i1.Mock implements _i21.ZoomState { @override double get minZoomRatio => (super.noSuchMethod( Invocation.getter(#minZoomRatio), @@ -1322,31 +1427,31 @@ class MockZoomState extends _i1.Mock implements _i19.ZoomState { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveCameraState extends _i1.Mock - implements _i4.LiveData<_i18.CameraState> { + implements _i4.LiveData<_i20.CameraState> { MockLiveCameraState() { _i1.throwOnMissingStub(this); } @override - _i16.Future observe(_i28.Observer<_i18.CameraState>? observer) => + _i17.Future observe(_i32.Observer<_i20.CameraState>? observer) => (super.noSuchMethod( Invocation.method( #observe, [observer], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future removeObservers() => (super.noSuchMethod( + _i17.Future removeObservers() => (super.noSuchMethod( Invocation.method( #removeObservers, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } /// A class which mocks [LiveData]. @@ -1354,29 +1459,29 @@ class MockLiveCameraState extends _i1.Mock /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveZoomState extends _i1.Mock - implements _i4.LiveData<_i19.ZoomState> { + implements _i4.LiveData<_i21.ZoomState> { MockLiveZoomState() { _i1.throwOnMissingStub(this); } @override - _i16.Future observe(_i28.Observer<_i19.ZoomState>? observer) => + _i17.Future observe(_i32.Observer<_i21.ZoomState>? observer) => (super.noSuchMethod( Invocation.method( #observe, [observer], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future removeObservers() => (super.noSuchMethod( + _i17.Future removeObservers() => (super.noSuchMethod( Invocation.method( #removeObservers, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart index 09eb9436c7ad..58d89c49b20a 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart @@ -6,11 +6,13 @@ import 'dart:async' as _i3; import 'package:camera_android_camerax/src/camera_control.dart' as _i2; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5; -import 'package:camera_android_camerax/src/capture_request_options.dart' as _i4; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/capture_request_options.dart' as _i6; +import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i5; +import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i6; +import 'test_camerax_library.g.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -53,6 +55,37 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); + + @override + _i3.Future<_i4.FocusMeteringResult?> startFocusAndMetering( + _i5.FocusMeteringAction? action) => + (super.noSuchMethod( + Invocation.method( + #startFocusAndMetering, + [action], + ), + returnValue: _i3.Future<_i4.FocusMeteringResult?>.value(), + ) as _i3.Future<_i4.FocusMeteringResult?>); + + @override + _i3.Future cancelFocusAndMetering() => (super.noSuchMethod( + Invocation.method( + #cancelFocusAndMetering, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setExposureCompensationIndex(int? index) => + (super.noSuchMethod( + Invocation.method( + #setExposureCompensationIndex, + [index], + ), + returnValue: _i3.Future.value(), + ) as _i3.Future); } /// A class which mocks [CaptureRequestOptions]. @@ -60,36 +93,24 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockCaptureRequestOptions extends _i1.Mock - implements _i4.CaptureRequestOptions { + implements _i6.CaptureRequestOptions { MockCaptureRequestOptions() { _i1.throwOnMissingStub(this); } @override - List<(_i5.CaptureRequestKeySupportedType, dynamic)> get requestedOptions => + List<(_i7.CaptureRequestKeySupportedType, Object?)> get requestedOptions => (super.noSuchMethod( Invocation.getter(#requestedOptions), - returnValue: <(_i5.CaptureRequestKeySupportedType, dynamic)>[], - ) as List<(_i5.CaptureRequestKeySupportedType, dynamic)>); - - @override - set requestedOptions( - List<(_i5.CaptureRequestKeySupportedType, dynamic)>? - _requestedOptions) => - super.noSuchMethod( - Invocation.setter( - #requestedOptions, - _requestedOptions, - ), - returnValueForMissingStub: null, - ); + returnValue: <(_i7.CaptureRequestKeySupportedType, Object?)>[], + ) as List<(_i7.CaptureRequestKeySupportedType, Object?)>); } /// A class which mocks [TestCamera2CameraControlHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestCamera2CameraControlHostApi extends _i1.Mock - implements _i6.TestCamera2CameraControlHostApi { + implements _i8.TestCamera2CameraControlHostApi { MockTestCamera2CameraControlHostApi() { _i1.throwOnMissingStub(this); } @@ -132,7 +153,7 @@ class MockTestCamera2CameraControlHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i6.TestInstanceManagerHostApi { + implements _i8.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/camera/camera_android_camerax/test/camera_control_test.dart b/packages/camera/camera_android_camerax/test/camera_control_test.dart index 63c7a7d9aa9b..22ba8aaf2479 100644 --- a/packages/camera/camera_android_camerax/test/camera_control_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_control_test.dart @@ -6,6 +6,7 @@ import 'package:camera_android_camerax/src/camera_control.dart'; import 'package:camera_android_camerax/src/focus_metering_action.dart'; import 'package:camera_android_camerax/src/focus_metering_result.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -13,7 +14,11 @@ import 'package:mockito/mockito.dart'; import 'camera_control_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestCameraControlHostApi, TestInstanceManagerHostApi]) +@GenerateMocks([ + TestCameraControlHostApi, + TestInstanceManagerHostApi, + FocusMeteringAction +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -90,8 +95,7 @@ void main() { instanceManager: instanceManager, ); const int cameraControlIdentifier = 75; - final FocusMeteringAction action = - FocusMeteringAction.detached(instanceManager: instanceManager); + final FocusMeteringAction action = MockFocusMeteringAction(); const int actionId = 5; final FocusMeteringResult result = FocusMeteringResult.detached(instanceManager: instanceManager); @@ -105,8 +109,7 @@ void main() { instanceManager.addHostCreatedInstance( action, actionId, - onCopy: (_) => - FocusMeteringAction.detached(instanceManager: instanceManager), + onCopy: (_) => MockFocusMeteringAction(), ); instanceManager.addHostCreatedInstance( result, @@ -122,6 +125,41 @@ void main() { verify(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId)); }); + test('startFocusAndMetering returns null result if operation was canceled', + () async { + final MockTestCameraControlHostApi mockApi = + MockTestCameraControlHostApi(); + TestCameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraControl cameraControl = CameraControl.detached( + instanceManager: instanceManager, + ); + const int cameraControlIdentifier = 75; + final FocusMeteringAction action = MockFocusMeteringAction(); + const int actionId = 5; + + instanceManager.addHostCreatedInstance( + cameraControl, + cameraControlIdentifier, + onCopy: (_) => CameraControl.detached(instanceManager: instanceManager), + ); + instanceManager.addHostCreatedInstance( + action, + actionId, + onCopy: (_) => MockFocusMeteringAction(), + ); + + when(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId)) + .thenAnswer((_) => Future.value()); + + expect(await cameraControl.startFocusAndMetering(action), isNull); + verify(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId)); + }); + test( 'cancelFocusAndMetering makes call on Java side to cancel focus and metering', () async { @@ -182,6 +220,72 @@ void main() { mockApi.setExposureCompensationIndex(cameraControlIdentifier, index)); }); + test( + 'setExposureCompensationIndex returns null when operation was canceled', + () async { + final MockTestCameraControlHostApi mockApi = + MockTestCameraControlHostApi(); + TestCameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraControl cameraControl = CameraControl.detached( + instanceManager: instanceManager, + ); + const int cameraControlIdentifier = 40; + + instanceManager.addHostCreatedInstance( + cameraControl, + cameraControlIdentifier, + onCopy: (_) => CameraControl.detached(instanceManager: instanceManager), + ); + + const int index = 2; + when(mockApi.setExposureCompensationIndex(cameraControlIdentifier, index)) + .thenAnswer((_) => Future.value()); + + expect(await cameraControl.setExposureCompensationIndex(index), isNull); + verify( + mockApi.setExposureCompensationIndex(cameraControlIdentifier, index)); + }); + + test( + 'setExposureCompensationIndex throws PlatformException when one is thrown from native side', + () async { + final MockTestCameraControlHostApi mockApi = + MockTestCameraControlHostApi(); + TestCameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraControl cameraControl = CameraControl.detached( + instanceManager: instanceManager, + ); + const int cameraControlIdentifier = 40; + + instanceManager.addHostCreatedInstance( + cameraControl, + cameraControlIdentifier, + onCopy: (_) => CameraControl.detached(instanceManager: instanceManager), + ); + + const int index = 1; + when(mockApi.setExposureCompensationIndex(cameraControlIdentifier, index)) + .thenThrow(PlatformException( + code: 'TEST_ERROR', + details: 'Platform exception thrown from Java side.')); + + expect(() => cameraControl.setExposureCompensationIndex(index), + throwsA(isA())); + + verify( + mockApi.setExposureCompensationIndex(cameraControlIdentifier, index)); + }); + test('flutterApiCreate makes call to add instance to instance manager', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, diff --git a/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart index a11525af9867..57040bcac62b 100644 --- a/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart @@ -5,6 +5,8 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; +import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i4; +import 'package:camera_android_camerax/src/metering_point.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i2; @@ -66,7 +68,7 @@ class MockTestCameraControlHostApi extends _i1.Mock ) as _i3.Future); @override - _i3.Future startFocusAndMetering( + _i3.Future startFocusAndMetering( int? identifier, int? focusMeteringActionId, ) => @@ -78,8 +80,8 @@ class MockTestCameraControlHostApi extends _i1.Mock focusMeteringActionId, ], ), - returnValue: _i3.Future.value(0), - ) as _i3.Future); + returnValue: _i3.Future.value(), + ) as _i3.Future); @override _i3.Future cancelFocusAndMetering(int? identifier) => @@ -93,7 +95,7 @@ class MockTestCameraControlHostApi extends _i1.Mock ) as _i3.Future); @override - _i3.Future setExposureCompensationIndex( + _i3.Future setExposureCompensationIndex( int? identifier, int? index, ) => @@ -105,8 +107,8 @@ class MockTestCameraControlHostApi extends _i1.Mock index, ], ), - returnValue: _i3.Future.value(0), - ) as _i3.Future); + returnValue: _i3.Future.value(), + ) as _i3.Future); } /// A class which mocks [TestInstanceManagerHostApi]. @@ -127,3 +129,20 @@ class MockTestInstanceManagerHostApi extends _i1.Mock returnValueForMissingStub: null, ); } + +/// A class which mocks [FocusMeteringAction]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockFocusMeteringAction extends _i1.Mock + implements _i4.FocusMeteringAction { + MockFocusMeteringAction() { + _i1.throwOnMissingStub(this); + } + + @override + List<(_i5.MeteringPoint, int?)> get meteringPointInfos => (super.noSuchMethod( + Invocation.getter(#meteringPointInfos), + returnValue: <(_i5.MeteringPoint, int?)>[], + ) as List<(_i5.MeteringPoint, int?)>); +} diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart index 657fb1aa3da4..8a7dcdc6c117 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart @@ -37,11 +37,14 @@ void main() { ); FocusMeteringAction.detached( + meteringPointInfos: <(MeteringPoint, int?)>[ + (MockMeteringPoint(), FocusMeteringAction.flagAwb) + ], instanceManager: instanceManager, ); - verifyNever( - mockApi.create(argThat(isA()), argThat(isA>()))); + verifyNever(mockApi.create(argThat(isA()), argThat(isA>()), + argThat(isA()))); }); test('create calls create on the Java side', () { final MockTestFocusMeteringActionHostApi mockApi = @@ -64,6 +67,7 @@ void main() { (mockMeteringPoint1, mockMeteringPoint1Mode), (mockMeteringPoint2, mockMeteringPoint2Mode) ]; + const bool disableAutoCancel = true; instanceManager .addHostCreatedInstance(mockMeteringPoint1, mockMeteringPoint1Id, @@ -78,12 +82,14 @@ void main() { final FocusMeteringAction instance = FocusMeteringAction( meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel, instanceManager: instanceManager, ); final VerificationResult verificationResult = verify(mockApi.create( argThat(equals(instanceManager.getIdentifier(instance))), - captureAny)); + captureAny, + argThat(equals(disableAutoCancel)))); final List captureMeteringPointInfos = verificationResult.captured.single as List; expect(captureMeteringPointInfos.length, equals(2)); diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart index 717215ca228e..3b0e4c824076 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart @@ -1,13 +1,14 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/focus_metering_action_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i4; -import 'package:camera_android_camerax/src/metering_point.dart' as _i2; +import 'package:camera_android_camerax/src/camera_info.dart' as _i2; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5; +import 'package:camera_android_camerax/src/metering_point.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i3; +import 'test_camerax_library.g.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -22,11 +23,21 @@ import 'test_camerax_library.g.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo { + _FakeCameraInfo_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [MeteringPoint]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint { +class MockMeteringPoint extends _i1.Mock implements _i3.MeteringPoint { MockMeteringPoint() { _i1.throwOnMissingStub(this); } @@ -42,13 +53,22 @@ class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint { Invocation.getter(#y), returnValue: 0.0, ) as double); + + @override + _i2.CameraInfo get cameraInfo => (super.noSuchMethod( + Invocation.getter(#cameraInfo), + returnValue: _FakeCameraInfo_0( + this, + Invocation.getter(#cameraInfo), + ), + ) as _i2.CameraInfo); } /// A class which mocks [TestFocusMeteringActionHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestFocusMeteringActionHostApi extends _i1.Mock - implements _i3.TestFocusMeteringActionHostApi { + implements _i4.TestFocusMeteringActionHostApi { MockTestFocusMeteringActionHostApi() { _i1.throwOnMissingStub(this); } @@ -56,7 +76,8 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock @override void create( int? identifier, - List<_i4.MeteringPointInfo?>? meteringPointInfos, + List<_i5.MeteringPointInfo?>? meteringPointInfos, + bool? disableAutoCancel, ) => super.noSuchMethod( Invocation.method( @@ -64,6 +85,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock [ identifier, meteringPointInfos, + disableAutoCancel, ], ), returnValueForMissingStub: null, @@ -74,7 +96,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i3.TestInstanceManagerHostApi { + implements _i4.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart index d35cdc15efbe..be52b17bda47 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart @@ -1,12 +1,13 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/focus_metering_result_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:camera_android_camerax/src/metering_point.dart' as _i2; +import 'package:camera_android_camerax/src/camera_info.dart' as _i2; +import 'package:camera_android_camerax/src/metering_point.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i3; +import 'test_camerax_library.g.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -21,11 +22,21 @@ import 'test_camerax_library.g.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo { + _FakeCameraInfo_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [MeteringPoint]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint { +class MockMeteringPoint extends _i1.Mock implements _i3.MeteringPoint { MockMeteringPoint() { _i1.throwOnMissingStub(this); } @@ -41,13 +52,22 @@ class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint { Invocation.getter(#y), returnValue: 0.0, ) as double); + + @override + _i2.CameraInfo get cameraInfo => (super.noSuchMethod( + Invocation.getter(#cameraInfo), + returnValue: _FakeCameraInfo_0( + this, + Invocation.getter(#cameraInfo), + ), + ) as _i2.CameraInfo); } /// A class which mocks [TestFocusMeteringResultHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestFocusMeteringResultHostApi extends _i1.Mock - implements _i3.TestFocusMeteringResultHostApi { + implements _i4.TestFocusMeteringResultHostApi { MockTestFocusMeteringResultHostApi() { _i1.throwOnMissingStub(this); } @@ -66,7 +86,7 @@ class MockTestFocusMeteringResultHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i3.TestInstanceManagerHostApi { + implements _i4.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.dart b/packages/camera/camera_android_camerax/test/metering_point_test.dart index ba3daae96b10..5aa921643778 100644 --- a/packages/camera/camera_android_camerax/test/metering_point_test.dart +++ b/packages/camera/camera_android_camerax/test/metering_point_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:camera_android_camerax/src/metering_point.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,7 +12,8 @@ import 'package:mockito/mockito.dart'; import 'metering_point_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestInstanceManagerHostApi, TestMeteringPointHostApi]) +@GenerateMocks( + [TestInstanceManagerHostApi, TestMeteringPointHostApi, CameraInfo]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -33,11 +35,12 @@ void main() { x: 0, y: 0.3, size: 4, + cameraInfo: MockCameraInfo(), instanceManager: instanceManager, ); verifyNever(mockApi.create(argThat(isA()), argThat(isA()), - argThat(isA()), argThat(isA()))); + argThat(isA()), argThat(isA()), argThat(isA()))); }); test('create calls create on the Java side', () async { @@ -52,14 +55,27 @@ void main() { const double x = 0.5; const double y = 0.6; const double size = 3; + final CameraInfo mockCameraInfo = MockCameraInfo(); + const int mockCameraInfoId = 4; + + instanceManager.addHostCreatedInstance(mockCameraInfo, mockCameraInfoId, + onCopy: (CameraInfo original) => MockCameraInfo()); + MeteringPoint( x: x, y: y, size: size, + cameraInfo: mockCameraInfo, instanceManager: instanceManager, ); - verify(mockApi.create(argThat(isA()), x, y, size)); + verify(mockApi.create( + argThat(isA()), + x, + y, + size, + mockCameraInfoId, + )); }); test('getDefaultPointSize returns expected size', () async { diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart index ba199f66c63f..e7a3d9fadb13 100644 --- a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart @@ -1,11 +1,18 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/metering_point_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; + +import 'package:camera_android_camerax/src/camera_info.dart' as _i5; +import 'package:camera_android_camerax/src/camera_state.dart' as _i7; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i3; +import 'package:camera_android_camerax/src/live_data.dart' as _i2; +import 'package:camera_android_camerax/src/zoom_state.dart' as _i8; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i2; +import 'test_camerax_library.g.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -20,11 +27,32 @@ import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeLiveData_0 extends _i1.SmartFake + implements _i2.LiveData { + _FakeLiveData_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExposureState_1 extends _i1.SmartFake implements _i3.ExposureState { + _FakeExposureState_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [TestInstanceManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i2.TestInstanceManagerHostApi { + implements _i4.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } @@ -43,7 +71,7 @@ class MockTestInstanceManagerHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestMeteringPointHostApi extends _i1.Mock - implements _i2.TestMeteringPointHostApi { + implements _i4.TestMeteringPointHostApi { MockTestMeteringPointHostApi() { _i1.throwOnMissingStub(this); } @@ -54,6 +82,7 @@ class MockTestMeteringPointHostApi extends _i1.Mock double? x, double? y, double? size, + int? cameraInfoId, ) => super.noSuchMethod( Invocation.method( @@ -63,6 +92,7 @@ class MockTestMeteringPointHostApi extends _i1.Mock x, y, size, + cameraInfoId, ], ), returnValueForMissingStub: null, @@ -77,3 +107,70 @@ class MockTestMeteringPointHostApi extends _i1.Mock returnValue: 0.0, ) as double); } + +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCameraInfo extends _i1.Mock implements _i5.CameraInfo { + MockCameraInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Future getSensorRotationDegrees() => (super.noSuchMethod( + Invocation.method( + #getSensorRotationDegrees, + [], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future<_i2.LiveData<_i7.CameraState>> getCameraState() => + (super.noSuchMethod( + Invocation.method( + #getCameraState, + [], + ), + returnValue: _i6.Future<_i2.LiveData<_i7.CameraState>>.value( + _FakeLiveData_0<_i7.CameraState>( + this, + Invocation.method( + #getCameraState, + [], + ), + )), + ) as _i6.Future<_i2.LiveData<_i7.CameraState>>); + + @override + _i6.Future<_i3.ExposureState> getExposureState() => (super.noSuchMethod( + Invocation.method( + #getExposureState, + [], + ), + returnValue: _i6.Future<_i3.ExposureState>.value(_FakeExposureState_1( + this, + Invocation.method( + #getExposureState, + [], + ), + )), + ) as _i6.Future<_i3.ExposureState>); + + @override + _i6.Future<_i2.LiveData<_i8.ZoomState>> getZoomState() => (super.noSuchMethod( + Invocation.method( + #getZoomState, + [], + ), + returnValue: _i6.Future<_i2.LiveData<_i8.ZoomState>>.value( + _FakeLiveData_0<_i8.ZoomState>( + this, + Invocation.method( + #getZoomState, + [], + ), + )), + ) as _i6.Future<_i2.LiveData<_i8.ZoomState>>); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_filter_test.dart b/packages/camera/camera_android_camerax/test/resolution_filter_test.dart new file mode 100644 index 000000000000..07a96ff68027 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_filter_test.dart @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_filter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'resolution_filter_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + TestResolutionFilterHostApi, + TestInstanceManagerHostApi, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ResolutionFilter', () { + tearDown(() { + TestResolutionFilterHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test( + 'detached ResolutionFilter.onePreferredSize constructor does not make call to Host API createWithOnePreferredSize', + () { + final MockTestResolutionFilterHostApi mockApi = + MockTestResolutionFilterHostApi(); + TestResolutionFilterHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const double preferredWidth = 270; + const double preferredHeight = 720; + const Size preferredResolution = Size(preferredWidth, preferredHeight); + + ResolutionFilter.onePreferredSizeDetached( + preferredResolution: preferredResolution, + instanceManager: instanceManager, + ); + + verifyNever(mockApi.createWithOnePreferredSize( + argThat(isA()), + argThat(isA() + .having( + (ResolutionInfo size) => size.width, 'width', preferredWidth) + .having((ResolutionInfo size) => size.height, 'height', + preferredHeight)), + )); + }); + + test('HostApi createWithOnePreferredSize creates expected ResolutionFilter', + () { + final MockTestResolutionFilterHostApi mockApi = + MockTestResolutionFilterHostApi(); + TestResolutionFilterHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const double preferredWidth = 890; + const double preferredHeight = 980; + const Size preferredResolution = Size(preferredWidth, preferredHeight); + + final ResolutionFilter instance = ResolutionFilter.onePreferredSize( + preferredResolution: preferredResolution, + instanceManager: instanceManager, + ); + verify(mockApi.createWithOnePreferredSize( + instanceManager.getIdentifier(instance), + argThat(isA() + .having( + (ResolutionInfo size) => size.width, 'width', preferredWidth) + .having((ResolutionInfo size) => size.height, 'height', + preferredHeight)), + )); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_filter_test.mocks.dart b/packages/camera/camera_android_camerax/test/resolution_filter_test.mocks.dart new file mode 100644 index 000000000000..cf859c7b6378 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_filter_test.mocks.dart @@ -0,0 +1,67 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android_camerax/test/resolution_filter_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i2; + +// 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: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestResolutionFilterHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestResolutionFilterHostApi extends _i1.Mock + implements _i2.TestResolutionFilterHostApi { + MockTestResolutionFilterHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void createWithOnePreferredSize( + int? identifier, + _i3.ResolutionInfo? preferredResolution, + ) => + super.noSuchMethod( + Invocation.method( + #createWithOnePreferredSize, + [ + identifier, + preferredResolution, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i2.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_selector_test.dart b/packages/camera/camera_android_camerax/test/resolution_selector_test.dart index 45ecef576a3a..7aaa37ae1ea0 100644 --- a/packages/camera/camera_android_camerax/test/resolution_selector_test.dart +++ b/packages/camera/camera_android_camerax/test/resolution_selector_test.dart @@ -6,6 +6,7 @@ import 'dart:ui'; import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_filter.dart'; import 'package:camera_android_camerax/src/resolution_selector.dart'; import 'package:camera_android_camerax/src/resolution_strategy.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -17,6 +18,7 @@ import 'test_camerax_library.g.dart'; @GenerateMocks([ AspectRatioStrategy, + ResolutionFilter, ResolutionStrategy, TestResolutionSelectorHostApi, TestInstanceManagerHostApi, @@ -54,15 +56,13 @@ void main() { ResolutionSelector.detached( resolutionStrategy: MockResolutionStrategy(), + resolutionFilter: MockResolutionFilter(), aspectRatioStrategy: MockAspectRatioStrategy(), instanceManager: instanceManager, ); - verifyNever(mockApi.create( - argThat(isA()), - argThat(isA()), - argThat(isA()), - )); + verifyNever(mockApi.create(argThat(isA()), argThat(isA()), + argThat(isA()), argThat(isA()))); }); test('HostApi create creates expected ResolutionSelector instance', () { @@ -91,6 +91,20 @@ void main() { ), ); + final ResolutionFilter resolutionFilter = + ResolutionFilter.onePreferredSizeDetached( + preferredResolution: const Size(30, 40)); + const int resolutionFilterIdentifier = 54; + instanceManager.addHostCreatedInstance( + resolutionFilter, + resolutionFilterIdentifier, + onCopy: (ResolutionFilter original) => + ResolutionFilter.onePreferredSizeDetached( + preferredResolution: original.preferredResolution, + instanceManager: instanceManager, + ), + ); + final AspectRatioStrategy aspectRatioStrategy = AspectRatioStrategy.detached( preferredAspectRatio: AspectRatio.ratio4To3, @@ -110,6 +124,7 @@ void main() { final ResolutionSelector instance = ResolutionSelector( resolutionStrategy: resolutionStrategy, + resolutionFilter: resolutionFilter, aspectRatioStrategy: aspectRatioStrategy, instanceManager: instanceManager, ); @@ -117,6 +132,7 @@ void main() { verify(mockApi.create( instanceManager.getIdentifier(instance), resolutionStrategyIdentifier, + resolutionFilterIdentifier, aspectRatioStrategyIdentifier, )); }); diff --git a/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart index 541092f089a9..c36ae699e8ad 100644 --- a/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart @@ -3,11 +3,14 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i2; -import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i3; +import 'dart:ui' as _i2; + +import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i3; +import 'package:camera_android_camerax/src/resolution_filter.dart' as _i4; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i4; +import 'test_camerax_library.g.dart' as _i6; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -22,12 +25,22 @@ import 'test_camerax_library.g.dart' as _i4; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeSize_0 extends _i1.SmartFake implements _i2.Size { + _FakeSize_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [AspectRatioStrategy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockAspectRatioStrategy extends _i1.Mock - implements _i2.AspectRatioStrategy { + implements _i3.AspectRatioStrategy { MockAspectRatioStrategy() { _i1.throwOnMissingStub(this); } @@ -45,12 +58,31 @@ class MockAspectRatioStrategy extends _i1.Mock ) as int); } +/// A class which mocks [ResolutionFilter]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionFilter extends _i1.Mock implements _i4.ResolutionFilter { + MockResolutionFilter() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Size get preferredResolution => (super.noSuchMethod( + Invocation.getter(#preferredResolution), + returnValue: _FakeSize_0( + this, + Invocation.getter(#preferredResolution), + ), + ) as _i2.Size); +} + /// A class which mocks [ResolutionStrategy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionStrategy extends _i1.Mock - implements _i3.ResolutionStrategy { + implements _i5.ResolutionStrategy { MockResolutionStrategy() { _i1.throwOnMissingStub(this); } @@ -60,7 +92,7 @@ class MockResolutionStrategy extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestResolutionSelectorHostApi extends _i1.Mock - implements _i4.TestResolutionSelectorHostApi { + implements _i6.TestResolutionSelectorHostApi { MockTestResolutionSelectorHostApi() { _i1.throwOnMissingStub(this); } @@ -69,6 +101,7 @@ class MockTestResolutionSelectorHostApi extends _i1.Mock void create( int? identifier, int? resolutionStrategyIdentifier, + int? resolutionSelectorIdentifier, int? aspectRatioStrategyIdentifier, ) => super.noSuchMethod( @@ -77,6 +110,7 @@ class MockTestResolutionSelectorHostApi extends _i1.Mock [ identifier, resolutionStrategyIdentifier, + resolutionSelectorIdentifier, aspectRatioStrategyIdentifier, ], ), @@ -88,7 +122,7 @@ class MockTestResolutionSelectorHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i4.TestInstanceManagerHostApi { + implements _i6.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 4450f97c82d5..8080e4276fc8 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -1321,7 +1321,7 @@ abstract class TestResolutionSelectorHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int identifier, int? resolutionStrategyIdentifier, - int? aspectRatioStrategyIdentifier); + int? resolutionSelectorIdentifier, int? aspectRatioStrategyIdentifier); static void setup(TestResolutionSelectorHostApi? api, {BinaryMessenger? binaryMessenger}) { @@ -1343,8 +1343,12 @@ abstract class TestResolutionSelectorHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.ResolutionSelectorHostApi.create was null, expected non-null int.'); final int? arg_resolutionStrategyIdentifier = (args[1] as int?); - final int? arg_aspectRatioStrategyIdentifier = (args[2] as int?); - api.create(arg_identifier!, arg_resolutionStrategyIdentifier, + final int? arg_resolutionSelectorIdentifier = (args[2] as int?); + final int? arg_aspectRatioStrategyIdentifier = (args[3] as int?); + api.create( + arg_identifier!, + arg_resolutionStrategyIdentifier, + arg_resolutionSelectorIdentifier, arg_aspectRatioStrategyIdentifier); return []; }); @@ -1898,11 +1902,11 @@ abstract class TestCameraControlHostApi { Future setZoomRatio(int identifier, double ratio); - Future startFocusAndMetering(int identifier, int focusMeteringActionId); + Future startFocusAndMetering(int identifier, int focusMeteringActionId); Future cancelFocusAndMetering(int identifier); - Future setExposureCompensationIndex(int identifier, int index); + Future setExposureCompensationIndex(int identifier, int index); static void setup(TestCameraControlHostApi? api, {BinaryMessenger? binaryMessenger}) { @@ -1977,7 +1981,7 @@ abstract class TestCameraControlHostApi { final int? arg_focusMeteringActionId = (args[1] as int?); assert(arg_focusMeteringActionId != null, 'Argument for dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering was null, expected non-null int.'); - final int output = await api.startFocusAndMetering( + final int? output = await api.startFocusAndMetering( arg_identifier!, arg_focusMeteringActionId!); return [output]; }); @@ -2027,7 +2031,7 @@ abstract class TestCameraControlHostApi { final int? arg_index = (args[1] as int?); assert(arg_index != null, 'Argument for dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex was null, expected non-null int.'); - final int output = await api.setExposureCompensationIndex( + final int? output = await api.setExposureCompensationIndex( arg_identifier!, arg_index!); return [output]; }); @@ -2065,7 +2069,8 @@ abstract class TestFocusMeteringActionHostApi { static const MessageCodec codec = _TestFocusMeteringActionHostApiCodec(); - void create(int identifier, List meteringPointInfos); + void create(int identifier, List meteringPointInfos, + bool? disableAutoCancel); static void setup(TestFocusMeteringActionHostApi? api, {BinaryMessenger? binaryMessenger}) { @@ -2090,7 +2095,9 @@ abstract class TestFocusMeteringActionHostApi { (args[1] as List?)?.cast(); assert(arg_meteringPointInfos != null, 'Argument for dev.flutter.pigeon.FocusMeteringActionHostApi.create was null, expected non-null List.'); - api.create(arg_identifier!, arg_meteringPointInfos!); + final bool? arg_disableAutoCancel = (args[2] as bool?); + api.create( + arg_identifier!, arg_meteringPointInfos!, arg_disableAutoCancel); return []; }); } @@ -2138,7 +2145,8 @@ abstract class TestMeteringPointHostApi { TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); - void create(int identifier, double x, double y, double? size); + void create( + int identifier, double x, double y, double? size, int cameraInfoId); double getDefaultPointSize(); @@ -2168,7 +2176,11 @@ abstract class TestMeteringPointHostApi { assert(arg_y != null, 'Argument for dev.flutter.pigeon.MeteringPointHostApi.create was null, expected non-null double.'); final double? arg_size = (args[3] as double?); - api.create(arg_identifier!, arg_x!, arg_y!, arg_size); + final int? arg_cameraInfoId = (args[4] as int?); + assert(arg_cameraInfoId != null, + 'Argument for dev.flutter.pigeon.MeteringPointHostApi.create was null, expected non-null int.'); + api.create( + arg_identifier!, arg_x!, arg_y!, arg_size, arg_cameraInfoId!); return []; }); } @@ -2351,3 +2363,68 @@ abstract class TestCamera2CameraControlHostApi { } } } + +class _TestResolutionFilterHostApiCodec extends StandardMessageCodec { + const _TestResolutionFilterHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ResolutionInfo) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ResolutionInfo.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestResolutionFilterHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = + _TestResolutionFilterHostApiCodec(); + + void createWithOnePreferredSize( + int identifier, ResolutionInfo preferredResolution); + + static void setup(TestResolutionFilterHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ResolutionFilterHostApi.createWithOnePreferredSize', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ResolutionFilterHostApi.createWithOnePreferredSize 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.ResolutionFilterHostApi.createWithOnePreferredSize was null, expected non-null int.'); + final ResolutionInfo? arg_preferredResolution = + (args[1] as ResolutionInfo?); + assert(arg_preferredResolution != null, + 'Argument for dev.flutter.pigeon.ResolutionFilterHostApi.createWithOnePreferredSize was null, expected non-null ResolutionInfo.'); + api.createWithOnePreferredSize( + arg_identifier!, arg_preferredResolution!); + return []; + }); + } + } + } +} diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 98db153b11f0..d70fd9e73c6a 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.14+1 + +* Fixes bug where max resolution preset does not produce highest available resolution on iOS. + ## 0.9.14 * Adds support to HEIF format. diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index df0879fad89d..ac2e7ffb6ec4 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 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 */; }; + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; @@ -89,6 +90,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9C5CC6CAD53AD388B2694F3A /* 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 = ""; }; A24F9E418BA48BCC7409B117 /* 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 = ""; }; + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = CameraSessionPresetsTests.m; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraCaptureSessionQueueRaceConditionTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; @@ -151,6 +153,7 @@ E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, + CEF661192B5E36A500D33FD4 /* CameraSessionPresetsTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -451,6 +454,7 @@ F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, + CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */, E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m new file mode 100644 index 000000000000..a5130ad8288e --- /dev/null +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import camera_avfoundation; +@import camera_avfoundation.Test; + +@import AVFoundation; +@import XCTest; +#import +#import "CameraTestUtils.h" + +/// Includes test cases related to resolution presets setting operations for FLTCam class. +@interface FLTCamSessionPresetsTest : XCTestCase +@end + +@implementation FLTCamSessionPresetsTest + +- (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { + NSString *expectedPreset = AVCaptureSessionPresetInputPriority; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + + id captureFormatMock = OCMClassMock([AVCaptureDeviceFormat class]); + id captureDeviceMock = OCMClassMock([AVCaptureDevice class]); + OCMStub([captureDeviceMock formats]).andReturn(@[ captureFormatMock ]); + + OCMExpect([captureDeviceMock activeFormat]).andReturn(captureFormatMock); + OCMExpect([captureDeviceMock lockForConfiguration:NULL]).andReturn(YES); + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); + + FLTCreateCamWithVideoDimensionsForFormat(videoSessionMock, @"max", captureDeviceMock, + ^CMVideoDimensions(AVCaptureDeviceFormat *format) { + CMVideoDimensions videoDimensions; + videoDimensions.width = 1; + videoDimensions.height = 1; + return videoDimensions; + }); + + OCMVerifyAll(captureDeviceMock); + OCMVerifyAll(videoSessionMock); +} + +- (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { + NSString *expectedPreset = AVCaptureSessionPreset3840x2160; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + + // Make sure that setting resolution preset for session always succeeds. + OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); + + FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); + + OCMVerifyAll(videoSessionMock); +} + +- (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { + NSString *expectedPreset = AVCaptureSessionPreset3840x2160; + + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); + + // Make sure that setting resolution preset for session always succeeds. + OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. + OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); + + FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); + + OCMVerifyAll(videoSessionMock); +} + +@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 0c7e62f9fbb5..cdc11bff6c82 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -11,6 +11,24 @@ NS_ASSUME_NONNULL_BEGIN /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); +/// Creates an `FLTCam` with a given captureSession and resolutionPreset +/// @param captureSession AVCaptureSession for video +/// @param resolutionPreset preset for camera's captureSession resolution +/// @return an FLTCam object. +extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, + NSString *resolutionPreset); + +/// Creates an `FLTCam` with a given captureSession and resolutionPreset. +/// Allows to inject a capture device and a block to compute the video dimensions. +/// @param captureSession AVCaptureSession for video +/// @param resolutionPreset preset for camera's captureSession resolution +/// @param captureDevice AVCaptureDevice to be used +/// @param videoDimensionsForFormat custom code to determine video dimensions +/// @return an FLTCam object. +extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( + AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, + VideoDimensionsForFormat videoDimensionsForFormat); + /// Creates a test sample buffer. /// @return a test sample buffer. extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index bb98f7cf71e9..d0456f7aa544 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -12,11 +12,11 @@ .andReturn(inputMock); id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" @@ -29,6 +29,51 @@ error:nil]; } +FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, + NSString *resolutionPreset) { + id inputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) + .andReturn(inputMock); + + id audioSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); + OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + return [[FLTCam alloc] initWithCameraName:@"camera" + resolutionPreset:resolutionPreset + enableAudio:true + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + error:nil]; +} + +FLTCam *FLTCreateCamWithVideoDimensionsForFormat( + AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, + VideoDimensionsForFormat videoDimensionsForFormat) { + id inputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) + .andReturn(inputMock); + + id audioSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); + OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + return + [[FLTCam alloc] initWithResolutionPreset:resolutionPreset + enableAudio:true + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + captureDeviceFactory:^AVCaptureDevice *(void) { + return captureDevice; + } + videoDimensionsForFormat:videoDimensionsForFormat + error:nil]; +} + CMSampleBufferRef FLTCreateTestSampleBuffer(void) { CVPixelBufferRef pixelBuffer; CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer); diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h index aef8fca535a4..406f36af501c 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h @@ -9,9 +9,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - flash mode -/** - * Represents camera's flash mode. Mirrors `FlashMode` enum in flash_mode.dart. - */ +/// Represents camera's flash mode. Mirrors `FlashMode` enum in flash_mode.dart. typedef NS_ENUM(NSInteger, FLTFlashMode) { FLTFlashModeOff, FLTFlashModeAuto, @@ -22,23 +20,17 @@ typedef NS_ENUM(NSInteger, FLTFlashMode) { FLTFlashModeInvalid, }; -/** - * Gets FLTFlashMode from its string representation. - * @param mode a string representation of the FLTFlashMode. - */ +/// Gets FLTFlashMode from its string representation. +/// @param mode a string representation of the FLTFlashMode. extern FLTFlashMode FLTGetFLTFlashModeForString(NSString *mode); -/** - * Gets AVCaptureFlashMode from FLTFlashMode. - * @param mode flash mode. - */ +/// Gets AVCaptureFlashMode from FLTFlashMode. +/// @param mode flash mode. extern AVCaptureFlashMode FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashMode mode); #pragma mark - exposure mode -/** - * Represents camera's exposure mode. Mirrors ExposureMode in camera.dart. - */ +/// Represents camera's exposure mode. Mirrors ExposureMode in camera.dart. typedef NS_ENUM(NSInteger, FLTExposureMode) { FLTExposureModeAuto, FLTExposureModeLocked, @@ -47,23 +39,17 @@ typedef NS_ENUM(NSInteger, FLTExposureMode) { FLTExposureModeInvalid, }; -/** - * Gets a string representation of exposure mode. - * @param mode exposure mode - */ +/// Gets a string representation of exposure mode. +/// @param mode exposure mode extern NSString *FLTGetStringForFLTExposureMode(FLTExposureMode mode); -/** - * Gets FLTExposureMode from its string representation. - * @param mode a string representation of the FLTExposureMode. - */ +/// Gets FLTExposureMode from its string representation. +/// @param mode a string representation of the FLTExposureMode. extern FLTExposureMode FLTGetFLTExposureModeForString(NSString *mode); #pragma mark - focus mode -/** - * Represents camera's focus mode. Mirrors FocusMode in camera.dart. - */ +/// Represents camera's focus mode. Mirrors FocusMode in camera.dart. typedef NS_ENUM(NSInteger, FLTFocusMode) { FLTFocusModeAuto, FLTFocusModeLocked, @@ -72,35 +58,25 @@ typedef NS_ENUM(NSInteger, FLTFocusMode) { FLTFocusModeInvalid, }; -/** - * Gets a string representation from FLTFocusMode. - * @param mode focus mode - */ +/// Gets a string representation from FLTFocusMode. +/// @param mode focus mode extern NSString *FLTGetStringForFLTFocusMode(FLTFocusMode mode); -/** - * Gets FLTFocusMode from its string representation. - * @param mode a string representation of focus mode. - */ +/// Gets FLTFocusMode from its string representation. +/// @param mode a string representation of focus mode. extern FLTFocusMode FLTGetFLTFocusModeForString(NSString *mode); #pragma mark - device orientation -/** - * Gets UIDeviceOrientation from its string representation. - */ +/// Gets UIDeviceOrientation from its string representation. extern UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation); -/** - * Gets a string representation of UIDeviceOrientation. - */ +/// Gets a string representation of UIDeviceOrientation. extern NSString *FLTGetStringForUIDeviceOrientation(UIDeviceOrientation orientation); #pragma mark - resolution preset -/** - * Represents camera's resolution present. Mirrors ResolutionPreset in camera.dart. - */ +/// Represents camera's resolution present. Mirrors ResolutionPreset in camera.dart. typedef NS_ENUM(NSInteger, FLTResolutionPreset) { FLTResolutionPresetVeryLow, FLTResolutionPresetLow, @@ -114,22 +90,16 @@ typedef NS_ENUM(NSInteger, FLTResolutionPreset) { FLTResolutionPresetInvalid, }; -/** - * Gets FLTResolutionPreset from its string representation. - * @param preset a string representation of FLTResolutionPreset. - */ +/// Gets FLTResolutionPreset from its string representation. +/// @param preset a string representation of FLTResolutionPreset. extern FLTResolutionPreset FLTGetFLTResolutionPresetForString(NSString *preset); #pragma mark - video format -/** - * Gets VideoFormat from its string representation. - */ +/// Gets VideoFormat from its string representation. extern OSType FLTGetVideoFormatFromString(NSString *videoFormatString); -/** - * Represents image format. Mirrors ImageFileFormat in camera.dart. - */ +/// Represents image format. Mirrors ImageFileFormat in camera.dart. typedef NS_ENUM(NSInteger, FCPFileFormat) { FCPFileFormatJPEG, FCPFileFormatHEIF, @@ -138,9 +108,7 @@ typedef NS_ENUM(NSInteger, FCPFileFormat) { #pragma mark - image extension -/** - * Gets a string representation of ImageFileFormat. - */ +/// Gets a string representation of ImageFileFormat. extern FCPFileFormat FCPGetFileFormatFromString(NSString *fileFormatString); NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index f5979a829ca1..5234233f4c2e 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -14,9 +14,7 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A class that manages camera's state and performs camera operations. - */ +/// A class that manages camera's state and performs camera operations. @interface FLTCam : NSObject @property(readonly, nonatomic) AVCaptureDevice *captureDevice; @@ -45,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; + - (void)start; - (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; @@ -52,13 +51,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)close; - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; - (void)setImageFileFormat:(FCPFileFormat)fileFormat; -/** - * Starts recording a video with an optional streaming messenger. - * If the messenger is non-null then it will be called for each - * captured frame, allowing streaming concurrently with recording. - * - * @param messenger Nullable messenger for capturing each frame. - */ +/// Starts recording a video with an optional streaming messenger. +/// If the messenger is non-null then it will be called for each +/// captured frame, allowing streaming concurrently with recording. +/// +/// @param messenger Nullable messenger for capturing each frame. - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result messengerForStreaming:(nullable NSObject *)messenger; - (void)stopVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; @@ -72,28 +69,24 @@ NS_ASSUME_NONNULL_BEGIN - (void)setFocusModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr; - (void)applyFocusMode; -/** - * Acknowledges the receipt of one image stream frame. - * - * This should be called each time a frame is received. Failing to call it may - * cause later frames to be dropped instead of streamed. - */ +/// Acknowledges the receipt of one image stream frame. +/// +/// This should be called each time a frame is received. Failing to call it may +/// cause later frames to be dropped instead of streamed. - (void)receivedImageStreamData; -/** - * Applies FocusMode on the AVCaptureDevice. - * - * If the @c focusMode is set to FocusModeAuto the AVCaptureDevice is configured to use - * AVCaptureFocusModeContinuousModeAutoFocus when supported, otherwise it is set to - * AVCaptureFocusModeAutoFocus. If neither AVCaptureFocusModeContinuousModeAutoFocus nor - * AVCaptureFocusModeAutoFocus are supported focus mode will not be set. - * If @c focusMode is set to FocusModeLocked the AVCaptureDevice is configured to use - * AVCaptureFocusModeAutoFocus. If AVCaptureFocusModeAutoFocus is not supported focus mode will not - * be set. - * - * @param focusMode The focus mode that should be applied to the @captureDevice instance. - * @param captureDevice The AVCaptureDevice to which the @focusMode will be applied. - */ +/// Applies FocusMode on the AVCaptureDevice. +/// +/// If the @c focusMode is set to FocusModeAuto the AVCaptureDevice is configured to use +/// AVCaptureFocusModeContinuousModeAutoFocus when supported, otherwise it is set to +/// AVCaptureFocusModeAutoFocus. If neither AVCaptureFocusModeContinuousModeAutoFocus nor +/// AVCaptureFocusModeAutoFocus are supported focus mode will not be set. +/// If @c focusMode is set to FocusModeLocked the AVCaptureDevice is configured to use +/// AVCaptureFocusModeAutoFocus. If AVCaptureFocusModeAutoFocus is not supported focus mode will not +/// be set. +/// +/// @param focusMode The focus mode that should be applied to the @captureDevice instance. +/// @param captureDevice The AVCaptureDevice to which the @focusMode will be applied. - (void)applyFocusMode:(FLTFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice; - (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result; - (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 6f5040f2a1e1..b16d65fe40e3 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -86,6 +86,11 @@ @interface FLTCam () maxPixelCount) { + maxPixelCount = pixelCount; + bestFormat = format; + } + } + return bestFormat; +} + - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { @@ -935,7 +1001,7 @@ - (void)setDescriptionWhileRecording:(NSString *)cameraName return; } - _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; + _captureDevice = self.captureDeviceFactory(); AVCaptureConnection *oldConnection = [_captureVideoOutput connectionWithMediaType:AVMediaTypeVideo]; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h index acc64846cb2c..94993feaa74e 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h @@ -5,6 +5,14 @@ #import "FLTCam.h" #import "FLTSavePhotoDelegate.h" +/// Determines the video dimensions (width and height) for a given capture device format. +/// Used in tests to mock CMVideoFormatDescriptionGetDimensions. +typedef CMVideoDimensions (^VideoDimensionsForFormat)(AVCaptureDeviceFormat *); + +/// Factory block returning an AVCaptureDevice. +/// Used in tests to inject a device into FLTCam. +typedef AVCaptureDevice * (^CaptureDeviceFactory)(void); + @interface FLTImageStreamHandler : NSObject /// The queue on which `eventSink` property should be accessed. @@ -55,6 +63,19 @@ captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; +/// Initializes a camera instance. +/// Allows for testing with specified resolution, audio preference, orientation, +/// and direct access to capture sessions and blocks. +- (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset + enableAudio:(BOOL)enableAudio + orientation:(UIDeviceOrientation)orientation + videoCaptureSession:(AVCaptureSession *)videoCaptureSession + audioCaptureSession:(AVCaptureSession *)audioCaptureSession + captureSessionQueue:(dispatch_queue_t)captureSessionQueue + captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory + videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat + error:(NSError **)error; + /// Start streaming images. - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.h b/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.h index 40e4562e4483..cec35d28f40b 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.h @@ -18,18 +18,14 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^FLTSavePhotoDelegateCompletionHandler)(NSString *_Nullable path, NSError *_Nullable error); -/** - Delegate object that handles photo capture results. - */ +/// Delegate object that handles photo capture results. @interface FLTSavePhotoDelegate : NSObject -/** - * Initialize a photo capture delegate. - * @param path the path for captured photo file. - * @param ioQueue the queue on which captured photos are written to disk. - * @param completionHandler The completion handler block for save photo operations. Can - * be called from either main queue or IO queue. - */ +/// Initialize a photo capture delegate. +/// @param path the path for captured photo file. +/// @param ioQueue the queue on which captured photos are written to disk. +/// @param completionHandler The completion handler block for save photo operations. Can +/// be called from either main queue or IO queue. - (instancetype)initWithPath:(NSString *)path ioQueue:(dispatch_queue_t)ioQueue completionHandler:(FLTSavePhotoDelegateCompletionHandler)completionHandler; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate_Test.h b/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate_Test.h index 80e8f77a3b0e..79539e4bd40e 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate_Test.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate_Test.h @@ -4,9 +4,7 @@ #import "FLTSavePhotoDelegate.h" -/** - API exposed for unit tests. - */ +/// API exposed for unit tests. @interface FLTSavePhotoDelegate () /// The completion handler block for capture and save photo operations. diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.h b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.h index ddfa75487a28..20a1d4023a31 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.h @@ -6,22 +6,16 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A thread safe wrapper for FlutterEventChannel that can be called from any thread, by dispatching - * its underlying engine calls to the main thread. - */ +/// A thread safe wrapper for FlutterEventChannel that can be called from any thread, by dispatching +/// its underlying engine calls to the main thread. @interface FLTThreadSafeEventChannel : NSObject -/** - * Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. - * @param channel The FlutterEventChannel object to be wrapped. - */ +/// Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. +/// @param channel The FlutterEventChannel object to be wrapped. - (instancetype)initWithEventChannel:(FlutterEventChannel *)channel; -/* - * Registers a handler on the main thread for stream setup requests from the Flutter side. - # The completion block runs on the main thread. - */ +/// Registers a handler on the main thread for stream setup requests from the Flutter side. +/// The completion block runs on the main thread. - (void)setStreamHandler:(nullable NSObject *)handler completion:(void (^)(void))completion; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.h b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.h index 6677505671a3..09c4f43d535f 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.h @@ -6,56 +6,38 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A thread safe wrapper for FlutterResult that can be called from any thread, by dispatching its - * underlying engine calls to the main thread. - */ +/// A thread safe wrapper for FlutterResult that can be called from any thread, by dispatching its +/// underlying engine calls to the main thread. @interface FLTThreadSafeFlutterResult : NSObject -/** - * Gets the original FlutterResult object wrapped by this FLTThreadSafeFlutterResult instance. - */ +/// Gets the original FlutterResult object wrapped by this FLTThreadSafeFlutterResult instance. @property(readonly, nonatomic) FlutterResult flutterResult; -/** - * Initializes with a FlutterResult object. - * @param result The FlutterResult object that the result will be given to. - */ +/// Initializes with a FlutterResult object. +/// @param result The FlutterResult object that the result will be given to. - (instancetype)initWithResult:(FlutterResult)result; -/** - * Sends a successful result on the main thread without any data. - */ +/// Sends a successful result on the main thread without any data. - (void)sendSuccess; -/** - * Sends a successful result on the main thread with data. - * @param data Result data that is send to the Flutter Dart side. - */ +/// Sends a successful result on the main thread with data. +/// @param data Result data that is send to the Flutter Dart side. - (void)sendSuccessWithData:(id)data; -/** - * Sends an NSError as result on the main thread. - * @param error Error that will be send as FlutterError. - */ +/// Sends an NSError as result on the main thread. +/// @param error Error that will be send as FlutterError. - (void)sendError:(NSError *)error; -/** - * Sends a FlutterError as result on the main thread. - * @param flutterError FlutterError that will be sent to the Flutter Dart side. - */ +/// Sends a FlutterError as result on the main thread. +/// @param flutterError FlutterError that will be sent to the Flutter Dart side. - (void)sendFlutterError:(FlutterError *)flutterError; -/** - * Sends a FlutterError as result on the main thread. - */ +/// Sends a FlutterError as result on the main thread. - (void)sendErrorWithCode:(NSString *)code message:(nullable NSString *)message details:(nullable id)details; -/** - * Sends FlutterMethodNotImplemented as result on the main thread. - */ +/// Sends FlutterMethodNotImplemented as result on the main thread. - (void)sendNotImplemented; @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.m b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.m index 283a0d6bc164..ee636cdd867d 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.m @@ -47,9 +47,7 @@ - (void)sendNotImplemented { [self send:FlutterMethodNotImplemented]; } -/** - * Sends result to flutterResult on the main thread. - */ +/// Sends result to flutterResult on the main thread. - (void)send:(id _Nullable)result { FLTEnsureToRunOnMainQueue(^{ // WARNING: Should not use weak self, because `FlutterResult`s are passed as arguments diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.h b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.h index 0f6611db03ce..1ca0a7312e45 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.h @@ -6,21 +6,15 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A thread safe wrapper for FlutterMethodChannel that can be called from any thread, by dispatching - * its underlying engine calls to the main thread. - */ +/// A thread safe wrapper for FlutterMethodChannel that can be called from any thread, by +/// dispatching its underlying engine calls to the main thread. @interface FLTThreadSafeMethodChannel : NSObject -/** - * Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. - * @param channel The FlutterMethodChannel object to be wrapped. - */ +/// Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. +/// @param channel The FlutterMethodChannel object to be wrapped. - (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel; -/** - * Invokes the specified flutter method on the main thread with the specified arguments. - */ +/// Invokes the specified flutter method on the main thread with the specified arguments. - (void)invokeMethod:(NSString *)method arguments:(nullable id)arguments; @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h index 030e2dbc7818..2f80f684e426 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h @@ -6,39 +6,29 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A thread safe wrapper for FlutterTextureRegistry that can be called from any thread, by - * dispatching its underlying engine calls to the main thread. - */ +/// A thread safe wrapper for FlutterTextureRegistry that can be called from any thread, by +/// dispatching its underlying engine calls to the main thread. @interface FLTThreadSafeTextureRegistry : NSObject -/** - * Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to - * FlutterTextureRegistry. - * @param registry The FlutterTextureRegistry object to be wrapped. - */ +/// Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to +/// FlutterTextureRegistry. +/// @param registry The FlutterTextureRegistry object to be wrapped. - (instancetype)initWithTextureRegistry:(NSObject *)registry; -/** - * Registers a `FlutterTexture` on the main thread for usage in Flutter and returns an id that can - * be used to reference that texture when calling into Flutter with channels. - * - * On success the completion block completes with the pointer to the registered texture, else with - * 0. The completion block runs on the main thread. - */ +/// Registers a `FlutterTexture` on the main thread for usage in Flutter and returns an id that can +/// be used to reference that texture when calling into Flutter with channels. +/// +/// On success the completion block completes with the pointer to the registered texture, else with +/// 0. The completion block runs on the main thread. - (void)registerTexture:(NSObject *)texture completion:(void (^)(int64_t))completion; -/** - * Notifies the Flutter engine on the main thread that the given texture has been updated. - */ +/// Notifies the Flutter engine on the main thread that the given texture has been updated. - (void)textureFrameAvailable:(int64_t)textureId; -/** - * Notifies the Flutter engine on the main thread to unregister a `FlutterTexture` that has been - * previously registered with `registerTexture:`. - * @param textureId The result that was previously returned from `registerTexture:`. - */ +/// Notifies the Flutter engine on the main thread to unregister a `FlutterTexture` that has been +/// previously registered with `registerTexture:`. +/// @param textureId The result that was previously returned from `registerTexture:`. - (void)unregisterTexture:(int64_t)textureId; @end diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index fd9e4eeffa59..1d83c8567c61 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,8 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.14 + +version: 0.9.14+1 environment: sdk: ^3.2.3 diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 1db3ab335b91..836b73586df7 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,6 +1,8 @@ -## NEXT +## 2.7.4 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Documents `getExposureOffsetStepSize` to return -1 if the device does not support + exposure compensation. ## 2.7.3 diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 6707962cbc1f..c791d030ca2c 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -221,7 +221,8 @@ abstract class CameraPlatform extends PlatformInterface { /// Gets the supported step size for exposure offset for the selected camera in EV units. /// - /// Returns 0 when the camera supports using a free value without stepping. + /// Returns 0 when the camera supports using a free value without stepping and + /// returns -1 when exposure compensation is not supported. Future getExposureOffsetStepSize(int cameraId) { throw UnimplementedError('getMinExposureOffset() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index de4de97acff1..e139e2062db1 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%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: 2.7.3 +version: 2.7.4 environment: sdk: ^3.1.0 diff --git a/packages/camera/camera_web/example/integration_test/camera_web_test.dart b/packages/camera/camera_web/example/integration_test/camera_web_test.dart index 6c73bcf76bdc..56fb16cfab9a 100644 --- a/packages/camera/camera_web/example/integration_test/camera_web_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_web_test.dart @@ -2384,10 +2384,9 @@ void main() { testWidgets('onCameraResolutionChanged emits an empty stream', (WidgetTester tester) async { - expect( - CameraPlatform.instance.onCameraResolutionChanged(cameraId), - emits(isEmpty), - ); + final Stream stream = + CameraPlatform.instance.onCameraResolutionChanged(cameraId); + expect(await stream.isEmpty, isTrue); }); testWidgets( @@ -2968,20 +2967,18 @@ void main() { (WidgetTester tester) async { when(() => window.screen).thenReturn(null); - expect( - CameraPlatform.instance.onDeviceOrientationChanged(), - emits(isEmpty), - ); + final Stream stream = + CameraPlatform.instance.onDeviceOrientationChanged(); + expect(await stream.isEmpty, isTrue); }); testWidgets('when screen orientation is not supported', (WidgetTester tester) async { when(() => screen.orientation).thenReturn(null); - expect( - CameraPlatform.instance.onDeviceOrientationChanged(), - emits(isEmpty), - ); + final Stream stream = + CameraPlatform.instance.onDeviceOrientationChanged(); + expect(await stream.isEmpty, isTrue); }); }); diff --git a/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt index b2e4bd8d658b..4f2af69bbb09 100644 --- a/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/camera/camera_windows/example/windows/runner/Runner.rc b/packages/camera/camera_windows/example/windows/runner/Runner.rc index f1cfa4391ebd..06405f8e2355 100644 --- a/packages/camera/camera_windows/example/windows/runner/Runner.rc +++ b/packages/camera/camera_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index c6113b298146..2c4e4939615e 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT +## 0.3.4+1 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Removes a few deprecated API usages. + +## 0.3.4 + +* Updates to web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. ## 0.3.3+8 diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index 9eb95b448d88..8bc5361bbfb2 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -8,7 +8,7 @@ import 'dart:js_interop'; import 'dart:typed_data'; import 'package:meta/meta.dart'; -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; import '../web_helpers/web_helpers.dart'; import 'base.dart'; @@ -66,9 +66,7 @@ class XFile extends XFileBase { super(path) { if (path == null) { _browserBlob = _createBlobFromBytes(bytes, mimeType); - // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3 - // ignore: unnecessary_cast - _path = URL.createObjectURL(_browserBlob! as JSObject); + _path = URL.createObjectURL(_browserBlob!); } else { _path = path; } @@ -131,28 +129,30 @@ class XFile extends XFileBase { // 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. - // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 - // ignore: unnecessary_non_null_assertion - if (isSafari() && _length != null && _length! >= _fourGigabytes) { + if (isSafari() && _length != null && _length >= _fourGigabytes) { throw Exception('Safari cannot handle XFiles larger than 4GB.'); } - late XMLHttpRequest request; - try { - request = await HttpRequest.request(path, responseType: 'blob'); - } on ProgressEvent catch (e) { - if (e.type == 'error') { - throw Exception( - 'Could not load Blob from its URL. Has it been revoked?'); - } - rethrow; - } - - _browserBlob = request.response as Blob?; + final Completer blobCompleter = Completer(); - assert(_browserBlob != null, 'The Blob backing this XFile cannot be null!'); - - return _browserBlob!; + 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 diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart index ec9a2e86e7d7..bd50fc989502 100644 --- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart +++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; /// Create anchor element with download attribute HTMLAnchorElement createAnchorElement(String href, String? suggestedName) => @@ -20,11 +20,11 @@ void addElementToContainerAndClick(Element container, HTMLElement element) { /// Initializes a DOM container where elements can be injected. Element ensureInitialized(String id) { - Element? target = querySelector('#$id'); + Element? target = document.querySelector('#$id'); if (target == null) { final Element targetElement = document.createElement('flt-x-file')..id = id; - querySelector('body')!.appendChild(targetElement); + document.body!.appendChild(targetElement); target = targetElement; } return target; diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index c2b8f2f53424..cf8f9ca7d447 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -2,14 +2,14 @@ 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.3+8 +version: 0.3.4+1 environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: meta: ^1.3.0 - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: path: ^1.8.1 diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart index 4b002cac158b..dedd8806f01a 100644 --- a/packages/cross_file/test/x_file_html_test.dart +++ b/packages/cross_file/test/x_file_html_test.dart @@ -11,7 +11,7 @@ import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; import 'package:test/test.dart'; -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; const String expectedStringContents = 'Hello, world! I ❤ ñ! 空手'; final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); @@ -69,10 +69,10 @@ void main() { 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)! as html.Response; + await html.window.fetch(file.path.toJS).toDart; - final JSAny? arrayBuffer = await response.arrayBuffer().toDart; - final ByteBuffer data = (arrayBuffer! as JSArrayBuffer).toDart; + final JSAny arrayBuffer = await response.arrayBuffer().toDart; + final ByteBuffer data = (arrayBuffer as JSArrayBuffer).toDart; expect(data.asUint8List(), equals(bytes)); }); @@ -95,7 +95,7 @@ void main() { await file.saveTo(''); final html.Element? container = - html.querySelector('#$crossFileDomElementId'); + html.document.querySelector('#$crossFileDomElementId'); expect(container, isNotNull); }); @@ -106,7 +106,7 @@ void main() { await file.saveTo('path'); final html.Element container = - html.querySelector('#$crossFileDomElementId')!; + html.document.querySelector('#$crossFileDomElementId')!; late html.HTMLAnchorElement element; for (int i = 0; i < container.childNodes.length; i++) { diff --git a/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt b/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt +++ b/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt b/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt index b9e550fba8e1..17411a8ab8eb 100644 --- a/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt +++ b/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt @@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32 # 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") diff --git a/packages/dynamic_layouts/example/windows/runner/Runner.rc b/packages/dynamic_layouts/example/windows/runner/Runner.rc index 5fdea291cf19..0f5c0857111f 100644 --- a/packages/dynamic_layouts/example/windows/runner/Runner.rc +++ b/packages/dynamic_layouts/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/file_selector/file_selector/example/windows/flutter/CMakeLists.txt b/packages/file_selector/file_selector/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/file_selector/file_selector/example/windows/flutter/CMakeLists.txt +++ b/packages/file_selector/file_selector/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/file_selector/file_selector/example/windows/runner/CMakeLists.txt b/packages/file_selector/file_selector/example/windows/runner/CMakeLists.txt index b9e550fba8e1..17411a8ab8eb 100644 --- a/packages/file_selector/file_selector/example/windows/runner/CMakeLists.txt +++ b/packages/file_selector/file_selector/example/windows/runner/CMakeLists.txt @@ -20,6 +20,13 @@ add_executable(${BINARY_NAME} WIN32 # 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") diff --git a/packages/file_selector/file_selector/example/windows/runner/Runner.rc b/packages/file_selector/file_selector/example/windows/runner/Runner.rc index 5fdea291cf19..0f5c0857111f 100644 --- a/packages/file_selector/file_selector/example/windows/runner/Runner.rc +++ b/packages/file_selector/file_selector/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h index f71a8ae109e6..5935f037f6c4 100644 --- a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h +++ b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h @@ -9,14 +9,10 @@ // This header is available in the Test module. Import via "@import file_selector_ios.Test;". @interface FFSFileSelectorPlugin () -/** - * Overrides the view controller used for presenting the document picker. - */ +/// Overrides the view controller used for presenting the document picker. @property(nonatomic) UIViewController *_Nullable presentingViewControllerOverride; -/** - * Overrides the UIDocumentPickerViewController used for file picking. - */ +/// Overrides the UIDocumentPickerViewController used for file picking. @property(nonatomic) UIDocumentPickerViewController *_Nullable documentPickerViewControllerOverride; @end diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 40b7a853e5b3..692d1da7589a 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT +## 0.9.4+1 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Removes a few deprecated API usages. + +## 0.9.4 + +* Updates web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.16.0`. ## 0.9.3 diff --git a/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart b/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart index 1cec6fc7ad10..aa9dcdba187f 100644 --- a/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart +++ b/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart @@ -8,7 +8,7 @@ import 'package:file_selector_platform_interface/file_selector_platform_interfac import 'package:file_selector_web/src/dom_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; void main() { group('dom_helper', () { @@ -18,11 +18,10 @@ void main() { FileList? createFileList(List files) { final DataTransfer dataTransfer = DataTransfer(); + // Tear-offs of external extension type interop member 'add' are disallowed. + // ignore: prefer_foreach for (final File e in files) { - // TODO(srujzs): This is necessary in order to support package:web 0.4.0. - // This was not needed with 0.3.0, hence the lint. - // ignore: unnecessary_cast - dataTransfer.items.add(e as JSAny); + dataTransfer.items.add(e); } return dataTransfer.files; } @@ -42,17 +41,13 @@ void main() { setUp(() { domHelper = DomHelper(); - input = (createElementTag('input') as HTMLInputElement)..type = 'file'; + input = (document.createElement('input') as HTMLInputElement) + ..type = 'file'; }); group('getFiles', () { - final File mockFile1 = - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - File(['123456'].jsify as JSArray, 'file1.txt'); - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - final File mockFile2 = File([].jsify as JSArray, 'file2.txt'); + final File mockFile1 = File(['123456'.toJS].toJS, 'file1.txt'); + final File mockFile2 = File([].toJS, 'file2.txt'); testWidgets('works', (_) async { final Future> futureFiles = domHelper.getFiles( @@ -114,10 +109,7 @@ void main() { testWidgets('sets the attributes and clicks it', (_) async { const String accept = '.jpg,.png'; const bool multiple = true; - bool wasClicked = false; - - //ignore: unawaited_futures - input.onClick.first.then((_) => wasClicked = true); + final Future wasClicked = input.onClick.first.then((_) => true); final Future> futureFile = domHelper.getFiles( accept: accept, @@ -125,21 +117,19 @@ void main() { input: input, ); - expect(input.matches('body'), true); + expect(input.isConnected, true, + reason: 'input must be injected into the DOM'); expect(input.accept, accept); expect(input.multiple, multiple); - expect( - wasClicked, - true, - reason: - 'The should be clicked otherwise no dialog will be shown', - ); + expect(await wasClicked, true, + reason: + 'The should be clicked otherwise no dialog will be shown'); setFilesAndTriggerChange([]); await futureFile; // It should be already removed from the DOM after the file is resolved. - expect(input.parentElement, isNull); + expect(input.isConnected, isFalse); }); }); }); diff --git a/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart b/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart index f3d4c1ebf3e7..80376c5449fc 100644 --- a/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart +++ b/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart @@ -9,7 +9,7 @@ import 'package:file_selector_web/file_selector_web.dart'; import 'package:file_selector_web/src/dom_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; void main() { group('FileSelectorWeb', () { diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index 725ace61b00b..689ab97657e7 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -2,8 +2,8 @@ name: file_selector_web_integration_tests publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: file_selector_platform_interface: ^2.6.0 @@ -11,7 +11,7 @@ dependencies: path: ../ flutter: sdk: flutter - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/file_selector/file_selector_web/lib/src/dom_helper.dart b/packages/file_selector/file_selector_web/lib/src/dom_helper.dart index 7684a12286d7..c19748b92443 100644 --- a/packages/file_selector/file_selector_web/lib/src/dom_helper.dart +++ b/packages/file_selector/file_selector_web/lib/src/dom_helper.dart @@ -8,17 +8,17 @@ import 'dart:js_interop'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; /// Class to manipulate the DOM with the intention of reading files from it. class DomHelper { /// Default constructor, initializes the container DOM element. DomHelper() { - final Element body = querySelector('body')!; + final Element body = document.querySelector('body')!; body.appendChild(_container); } - final Element _container = createElementTag('file-selector'); + final Element _container = document.createElement('file-selector'); /// Sets the attributes and waits for a file to be selected. Future> getFiles({ @@ -28,7 +28,7 @@ class DomHelper { }) { final Completer> completer = Completer>(); final HTMLInputElement inputElement = - input ?? (createElementTag('input') as HTMLInputElement) + input ?? (document.createElement('input') as HTMLInputElement) ..type = 'file'; _container.appendChild( @@ -72,10 +72,7 @@ class DomHelper { } XFile _convertFileToXFile(File file) => XFile( - // TODO(srujzs): This is necessary in order to support package:web 0.4.0. - // This was not needed with 0.3.0, hence the lint. - // ignore: unnecessary_cast - URL.createObjectURL(file as JSObject), + URL.createObjectURL(file), name: file.name, length: file.size, lastModified: DateTime.fromMillisecondsSinceEpoch(file.lastModified), diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 3c3ca15c3aa9..2b4dc5cd7d9d 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -2,11 +2,11 @@ name: file_selector_web description: Web platform implementation of file_selector repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3 +version: 0.9.4+1 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,7 +22,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/file_selector/file_selector_windows/example/windows/flutter/CMakeLists.txt b/packages/file_selector/file_selector_windows/example/windows/flutter/CMakeLists.txt index b2e4bd8d658b..4f2af69bbb09 100644 --- a/packages/file_selector/file_selector_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/file_selector/file_selector_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/flutter_adaptive_scaffold/AUTHORS b/packages/flutter_adaptive_scaffold/AUTHORS index 0bbdf210f3c2..f53e3e1620fb 100644 --- a/packages/flutter_adaptive_scaffold/AUTHORS +++ b/packages/flutter_adaptive_scaffold/AUTHORS @@ -4,4 +4,5 @@ # Name/Organization Google Inc. -Jason C.H \ No newline at end of file +Jason C.H +Aliasgar Vohra \ No newline at end of file diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 623da45aaf86..065983360e9d 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,10 +1,19 @@ -## NEXT +## 0.1.10+1 +* Removes a broken design document link from the README. + +## 0.1.10 + +* FIX : Assertion added when tried with less than 2 destinations - [flutter/flutter#110902](https://github.com/flutter/flutter/issues/110902) + +## 0.1.9 + +* FIX : Drawer stays open even on destination tap - [flutter/flutter#141938](https://github.com/flutter/flutter/issues/141938) * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 0.1.8 -* Adds `transitionDuration` parameter for specifying how long the animation should be. +* Adds `transitionDuration` parameter for specifying how long the animation should be. ## 0.1.7+2 diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md index 2f3e186c0e0f..3dda09753536 100644 --- a/packages/flutter_adaptive_scaffold/README.md +++ b/packages/flutter_adaptive_scaffold/README.md @@ -247,8 +247,3 @@ return AdaptiveLayout( Both of the examples shown here produce the same output: !["Example of a display made with AdaptiveScaffold"](example/demo_files/adaptiveScaffold.gif) - -## Additional information - -You can find more information on this package and its usage in the public -[design doc](https://docs.google.com/document/d/1qhrpTWYs5f67X8v32NCCNTRMIjSrVHuaMEFAul-Q_Ms/edit?usp=sharing). diff --git a/packages/flutter_adaptive_scaffold/example/windows/flutter/CMakeLists.txt b/packages/flutter_adaptive_scaffold/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/flutter_adaptive_scaffold/example/windows/flutter/CMakeLists.txt +++ b/packages/flutter_adaptive_scaffold/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart index d134f73cb79f..663817a7ec16 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart @@ -103,7 +103,10 @@ class AdaptiveScaffold extends StatefulWidget { this.navigationRailWidth = 72, this.extendedNavigationRailWidth = 192, this.appBarBreakpoint, - }); + }) : assert( + destinations.length >= 2, + 'At least two destinations are required', + ); /// The destinations to be used in navigation items. These are converted to /// [NavigationRailDestination]s and [BottomNavigationBarItem]s and inserted @@ -502,12 +505,16 @@ class AdaptiveScaffold extends StatefulWidget { } class _AdaptiveScaffoldState extends State { + // Global scaffold key that will help to manage drawer state. + final GlobalKey _scaffoldKey = GlobalKey(); + @override Widget build(BuildContext context) { final NavigationRailThemeData navRailTheme = Theme.of(context).navigationRailTheme; return Scaffold( + key: _scaffoldKey, appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer || (widget.appBarBreakpoint?.isActive(context) ?? false) ? widget.appBar ?? AppBar() @@ -523,7 +530,7 @@ class _AdaptiveScaffoldState extends State { .map((NavigationDestination destination) => AdaptiveScaffold.toRailDestination(destination)) .toList(), - onDestinationSelected: widget.onSelectedIndexChange, + onDestinationSelected: _onDrawerDestinationSelected, ), ) : null, @@ -670,6 +677,20 @@ class _AdaptiveScaffoldState extends State { ), ); } + + void _onDrawerDestinationSelected(int index) { + if (widget.useDrawer) { + // If [useDrawer] is true, then retrieve the current state. + final ScaffoldState? scaffoldCurrentContext = _scaffoldKey.currentState; + if (scaffoldCurrentContext != null) { + if (scaffoldCurrentContext.isDrawerOpen) { + // If drawer is open, call [closeDrawer] to dismiss drawer as per material guidelines. + scaffoldCurrentContext.closeDrawer(); + } + } + } + widget.onSelectedIndexChange?.call(index); + } } class _BrickLayout extends StatelessWidget { diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index 6ebf6d894f92..cd322d41d2a3 100644 --- a/packages/flutter_adaptive_scaffold/pubspec.yaml +++ b/packages/flutter_adaptive_scaffold/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_adaptive_scaffold description: Widgets to easily build adaptive layouts, including navigation elements. -version: 0.1.8 +version: 0.1.10+1 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22 repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart index 3761fbfe29a0..8dcaf2d5a0a4 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart @@ -257,9 +257,9 @@ void main() { ]; await tester.pumpWidget( - const MaterialApp( + MaterialApp( home: MediaQuery( - data: MediaQueryData(size: Size(700, 900)), + data: const MediaQueryData(size: Size(700, 900)), child: AdaptiveScaffold( destinations: destinations, ), @@ -501,6 +501,119 @@ void main() { }, ); + testWidgets( + 'when drawer item tap, it shall close the already open drawer', + (WidgetTester tester) async { + //region Keys + const ValueKey firstDestinationIconKey = ValueKey( + 'first-normal-icon', + ); + const ValueKey firstDestinationSelectedIconKey = ValueKey( + 'first-selected-icon', + ); + const ValueKey lastDestinationIconKey = ValueKey( + 'last-normal-icon', + ); + const ValueKey lastDestinationSelectedIconKey = ValueKey( + 'last-selected-icon', + ); + //endregion + + //region Finder for destinations as per its icon. + final Finder lastDestinationWithIcon = find.byKey( + lastDestinationIconKey, + ); + final Finder lastDestinationWithSelectedIcon = find.byKey( + lastDestinationSelectedIconKey, + ); + //endregion + + const List destinations = [ + NavigationDestination( + icon: Icon( + Icons.inbox_outlined, + key: firstDestinationIconKey, + ), + selectedIcon: Icon( + Icons.inbox, + key: firstDestinationSelectedIconKey, + ), + label: 'Inbox', + ), + NavigationDestination( + icon: Icon( + Icons.video_call_outlined, + key: lastDestinationIconKey, + ), + selectedIcon: Icon( + Icons.video_call, + key: lastDestinationSelectedIconKey, + ), + label: 'Video', + ), + ]; + int selectedDestination = 0; + + await tester.pumpWidget( + MaterialApp( + home: MediaQuery( + data: const MediaQueryData(size: Size(450, 900)), + child: StatefulBuilder( + builder: ( + BuildContext context, + void Function(void Function()) setState, + ) { + return AdaptiveScaffold( + destinations: destinations, + selectedIndex: selectedDestination, + smallBreakpoint: TestBreakpoint400(), + drawerBreakpoint: TestBreakpoint400(), + onSelectedIndexChange: (int value) { + setState(() { + selectedDestination = value; + }); + }, + ); + }, + ), + ), + ), + ); + + expect(selectedDestination, 0); + Finder fDrawer = find.byType(Drawer); + Finder fNavigationRail = find.descendant( + of: fDrawer, + matching: find.byType(NavigationRail), + ); + expect(fDrawer, findsNothing); + expect(fNavigationRail, findsNothing); + + final ScaffoldState state = tester.state(find.byType(Scaffold)); + state.openDrawer(); + await tester.pumpAndSettle(Durations.short4); + expect(state.isDrawerOpen, isTrue); + + // Need to find again as Scaffold's state has been updated + fDrawer = find.byType(Drawer); + fNavigationRail = find.descendant( + of: fDrawer, + matching: find.byType(NavigationRail), + ); + expect(fDrawer, findsOneWidget); + expect(fNavigationRail, findsOneWidget); + + expect(lastDestinationWithIcon, findsOneWidget); + expect(lastDestinationWithSelectedIcon, findsNothing); + + await tester.ensureVisible(lastDestinationWithIcon); + await tester.tap(lastDestinationWithIcon); + await tester.pumpAndSettle(Durations.short4); + expect(selectedDestination, 1); + expect(state.isDrawerOpen, isFalse); + }, + ); + // This test checks whether AdaptiveScaffold.standardNavigationRail function // creates a NavigationRail widget as expected with groupAlignment provided, // and checks whether the NavigationRail's groupAlignment matches the expected value. @@ -604,6 +717,33 @@ void main() { expect(appBar, findsOneWidget); }, ); + + testWidgets( + 'When only one destination passed, shall throw assertion error', + (WidgetTester tester) async { + const List destinations = [ + NavigationDestination( + icon: Icon(Icons.inbox_outlined), + selectedIcon: Icon(Icons.inbox), + label: 'Inbox', + ), + ]; + + expect( + () => tester.pumpWidget( + MaterialApp( + home: MediaQuery( + data: const MediaQueryData(size: Size(700, 900)), + child: AdaptiveScaffold( + destinations: destinations, + ), + ), + ), + ), + throwsA(isA()), + ); + }, + ); } /// An empty widget that implements [PreferredSizeWidget] to ensure that diff --git a/packages/flutter_image/example/windows/flutter/CMakeLists.txt b/packages/flutter_image/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/flutter_image/example/windows/flutter/CMakeLists.txt +++ b/packages/flutter_image/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/flutter_lints/CHANGELOG.md b/packages/flutter_lints/CHANGELOG.md index 959080a0e1c6..5e5343cc446e 100644 --- a/packages/flutter_lints/CHANGELOG.md +++ b/packages/flutter_lints/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 3.0.2 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates various links in comments and documentation. ## 3.0.1 diff --git a/packages/flutter_lints/README.md b/packages/flutter_lints/README.md index 941fb47e7452..6617deecc816 100644 --- a/packages/flutter_lints/README.md +++ b/packages/flutter_lints/README.md @@ -42,7 +42,7 @@ linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. + # and their documentation is published at https://dart.dev/tools/linter-rules. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code @@ -54,7 +54,7 @@ linter: # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +# https://dart.dev/tools/analysis ``` ## Adding new lints @@ -71,7 +71,7 @@ bump. To keep churn low, lints are not added one-by-one, but in one batch following a review of all accumulated suggestions since the previous review. [Flutter]: https://flutter.dev -[dart analyzer]: https://dart.dev/guides/language/analysis-options +[dart analyzer]: https://dart.dev/tools/analysis [Dart-enabled IDEs]: https://dart.dev/tools#ides-and-editors [package:lints]: https://pub.dev/packages/lints [lint proposal]: https://github.com/dart-lang/lints/issues/new?&labels=type-lint&template=lint-propoposal.md diff --git a/packages/flutter_lints/example/analysis_options.yaml b/packages/flutter_lints/example/analysis_options.yaml index 0d2902135cae..6e937f6ea890 100644 --- a/packages/flutter_lints/example/analysis_options.yaml +++ b/packages/flutter_lints/example/analysis_options.yaml @@ -13,7 +13,7 @@ linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. + # and their documentation is published at https://dart.dev/tools/linter-rules. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code @@ -25,4 +25,4 @@ linter: # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +# https://dart.dev/tools/analysis diff --git a/packages/flutter_lints/pubspec.yaml b/packages/flutter_lints/pubspec.yaml index 860eedbb55ee..051e5cf2a095 100644 --- a/packages/flutter_lints/pubspec.yaml +++ b/packages/flutter_lints/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_lints description: Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices. repository: https://github.com/flutter/packages/tree/main/packages/flutter_lints issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_lints%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ^3.1.0 diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index 8feeca4bdae2..f3ed8654f361 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,6 +1,27 @@ -## NEXT +## 0.6.22 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Introduces a new `MarkdownElementBuilder.isBlockElement()` method to specify if custom element + is a block. + +## 0.6.21+1 + +* Adds `onSelectionChanged` to the constructors of `Markdown` and `MarkdownBody`. + +## 0.6.21 + +* Fixes support for `WidgetSpan` in `Text.rich` elements inside `MarkdownElementBuilder`. + +## 0.6.20+1 + +* Updates minimum supported SDK version to Flutter 3.19. + +## 0.6.20 + +* Adds `textScaler` to `MarkdownStyleSheet`, and deprecates `textScaleFactor`. + * Clients using `textScaleFactor: someFactor` should replace it with + `TextScaler.linear(someFactor)` to preserve behavior. +* Removes use of deprecated Flutter framework `textScaleFactor` methods. +* Updates minimum supported SDK version to Flutter 3.16. ## 0.6.19 @@ -44,7 +65,7 @@ * Introduces a new `MarkdownElementBuilder.visitElementAfterWithContext()` method passing the widget `BuildContext` and the parent text's `TextStyle`. - + ## 0.6.16 * Adds `tableVerticalAlignment` property to allow aligning table cells vertically. diff --git a/packages/flutter_markdown/README.md b/packages/flutter_markdown/README.md index 6a97d169e7fb..93cc6f50b8c3 100644 --- a/packages/flutter_markdown/README.md +++ b/packages/flutter_markdown/README.md @@ -72,6 +72,15 @@ but it's possible to create your own custom styling. Use the MarkdownStyle class to pass in your own style. If you don't want to use Markdown outside of material design, use the MarkdownRaw class. +## Selection + +By default, Markdown is not selectable. A caller may use the following ways to +customize the selection behavior of Markdown: + +* Set `selectable` to true, and use `onTapText` and `onSelectionChanged` to + handle tapping and selecting events. +* Set `selectable` to false, and wrap Markdown with [`SelectionArea`](https://api.flutter.dev/flutter/material/SelectionArea-class.html) or [`SelectionRegion`](https://api.flutter.dev/flutter/widgets/SelectableRegion-class.html). + ## Emoji Support Emoji glyphs can be included in the formatted text displayed by the Markdown diff --git a/packages/flutter_markdown/example/pubspec.yaml b/packages/flutter_markdown/example/pubspec.yaml index a904473786a4..c536ac0ce8d5 100644 --- a/packages/flutter_markdown/example/pubspec.yaml +++ b/packages/flutter_markdown/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the flutter_markdown package. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/flutter_markdown/example/windows/flutter/CMakeLists.txt b/packages/flutter_markdown/example/windows/flutter/CMakeLists.txt index 744f08a9389b..0a9177722722 100644 --- a/packages/flutter_markdown/example/windows/flutter/CMakeLists.txt +++ b/packages/flutter_markdown/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -90,7 +95,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/flutter_markdown/lib/src/_functions_io.dart b/packages/flutter_markdown/lib/src/_functions_io.dart index 81fd5b8f22b6..b3020573e165 100644 --- a/packages/flutter_markdown/lib/src/_functions_io.dart +++ b/packages/flutter_markdown/lib/src/_functions_io.dart @@ -64,7 +64,7 @@ final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?) } return result.copyWith( - textScaleFactor: MediaQuery.textScaleFactorOf(context), + textScaler: MediaQuery.textScalerOf(context), ); }; diff --git a/packages/flutter_markdown/lib/src/_functions_web.dart b/packages/flutter_markdown/lib/src/_functions_web.dart index 62692236a21e..abf23453225c 100644 --- a/packages/flutter_markdown/lib/src/_functions_web.dart +++ b/packages/flutter_markdown/lib/src/_functions_web.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html'; // ignore: avoid_web_libraries_in_flutter +import 'dart:js_interop'; import 'package:flutter/cupertino.dart' show CupertinoTheme; import 'package:flutter/material.dart' show Theme; @@ -50,23 +50,17 @@ final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?) BuildContext context, MarkdownStyleSheetBaseTheme? baseTheme, ) { - MarkdownStyleSheet result; - switch (baseTheme) { - case MarkdownStyleSheetBaseTheme.platform: - final String userAgent = window.navigator.userAgent; - result = userAgent.contains('Mac OS X') - ? MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context)) - : MarkdownStyleSheet.fromTheme(Theme.of(context)); - case MarkdownStyleSheetBaseTheme.cupertino: - result = - MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context)); - case MarkdownStyleSheetBaseTheme.material: - default: // ignore: no_default_cases - result = MarkdownStyleSheet.fromTheme(Theme.of(context)); - } + final MarkdownStyleSheet result = switch (baseTheme) { + MarkdownStyleSheetBaseTheme.platform + when _userAgent.toDart.contains('Mac OS X') => + MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context)), + MarkdownStyleSheetBaseTheme.cupertino => + MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context)), + _ => MarkdownStyleSheet.fromTheme(Theme.of(context)), + }; return result.copyWith( - textScaleFactor: MediaQuery.textScaleFactorOf(context), + textScaler: MediaQuery.textScalerOf(context), ); }; @@ -84,3 +78,6 @@ Widget _handleDataSchemeUri( } return const SizedBox(); } + +@JS('window.navigator.userAgent') +external JSString get _userAgent; diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index 659df8ae6fc7..739520588db7 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -2,18 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore: unnecessary_import, see https://github.com/flutter/flutter/pull/138881 -import 'dart:ui'; - import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:markdown/markdown.dart' as md; -import '_functions_io.dart' if (dart.library.html) '_functions_web.dart'; +import '_functions_io.dart' if (dart.library.js_interop) '_functions_web.dart'; import 'style_sheet.dart'; import 'widget.dart'; -const List _kBlockTags = [ +final List _kBlockTags = [ 'p', 'h1', 'h2', @@ -115,6 +112,7 @@ class MarkdownBuilder implements md.NodeVisitor { required this.paddingBuilders, required this.listItemCrossAxisAlignment, this.fitContent = false, + this.onSelectionChanged, this.onTapText, this.softLineBreak = false, }); @@ -158,6 +156,9 @@ class MarkdownBuilder implements md.NodeVisitor { /// does not allow for intrinsic height measurements. final MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment; + /// Called when the user changes selection when [selectable] is set to true. + final MarkdownOnSelectionChangedCallback? onSelectionChanged; + /// Default tap handler used when [selectable] is set to true final VoidCallback? onTapText; @@ -189,6 +190,12 @@ class MarkdownBuilder implements md.NodeVisitor { _linkHandlers.clear(); _isInBlockquote = false; + builders.forEach((String key, MarkdownElementBuilder value) { + if (value.isBlockElement()) { + _kBlockTags.add(key); + } + }); + _blocks.add(_BlockElement(null)); for (final md.Node node in nodes) { @@ -710,52 +717,119 @@ class MarkdownBuilder implements md.NodeVisitor { } } + /// Extracts all spans from an inline element and merges them into a single list + Iterable _getInlineSpans(InlineSpan span) { + // If the span is not a TextSpan or it has no children, return the span + if (span is! TextSpan || span.children == null) { + return [span]; + } + + // Merge the style of the parent with the style of the children + final Iterable spans = + span.children!.map((InlineSpan childSpan) { + if (childSpan is TextSpan) { + return TextSpan( + text: childSpan.text, + recognizer: childSpan.recognizer, + semanticsLabel: childSpan.semanticsLabel, + style: childSpan.style?.merge(span.style), + ); + } else { + return childSpan; + } + }); + + return spans; + } + /// Merges adjacent [TextSpan] children List _mergeInlineChildren( List children, TextAlign? textAlign, ) { + // List of merged text spans and widgets final List mergedTexts = []; + for (final Widget child in children) { - if (mergedTexts.isNotEmpty && mergedTexts.last is Text && child is Text) { - final Text previous = mergedTexts.removeLast() as Text; - final TextSpan previousTextSpan = previous.textSpan! as TextSpan; - final List children = previousTextSpan.children != null - ? previousTextSpan.children! - .map((InlineSpan span) => span is! TextSpan - ? TextSpan(children: [span]) - : span) - .toList() - : [previousTextSpan]; - children.add(child.textSpan! as TextSpan); - final TextSpan? mergedSpan = _mergeSimilarTextSpans(children); - mergedTexts.add(_buildRichText( - mergedSpan, - textAlign: textAlign, - )); - } else if (mergedTexts.isNotEmpty && - mergedTexts.last is SelectableText && - child is SelectableText) { - final SelectableText previous = - mergedTexts.removeLast() as SelectableText; - final TextSpan previousTextSpan = previous.textSpan!; - final List children = previousTextSpan.children != null - ? List.from(previousTextSpan.children!) - : [previousTextSpan]; - if (child.textSpan != null) { - children.add(child.textSpan!); + // If the list is empty, add the current widget to the list + if (mergedTexts.isEmpty) { + mergedTexts.add(child); + continue; + } + + // Remove last widget from the list to merge it with the current widget + final Widget last = mergedTexts.removeLast(); + + // Extracted spans from the last and the current widget + List spans = []; + + // Extract the text spans from the last widget + if (last is SelectableText) { + final TextSpan span = last.textSpan!; + spans.addAll(_getInlineSpans(span)); + } else if (last is Text) { + final InlineSpan span = last.textSpan!; + spans.addAll(_getInlineSpans(span)); + } else if (last is RichText) { + final InlineSpan span = last.text; + spans.addAll(_getInlineSpans(span)); + } else { + // If the last widget is not a text widget, + // add both the last and the current widget to the list + mergedTexts.addAll([last, child]); + continue; + } + + // Extract the text spans from the current widget + if (child is Text) { + final InlineSpan span = child.textSpan!; + spans.addAll(_getInlineSpans(span)); + } else if (child is SelectableText) { + final TextSpan span = child.textSpan!; + spans.addAll(_getInlineSpans(span)); + } else if (child is RichText) { + final InlineSpan span = child.text; + spans.addAll(_getInlineSpans(span)); + } else { + // If the current widget is not a text widget, + // add both the last and the current widget to the list + mergedTexts.addAll([last, child]); + continue; + } + + if (spans.isNotEmpty) { + // Merge similar text spans + spans = _mergeSimilarTextSpans(spans); + + // Create a new text widget with the merged text spans + InlineSpan child; + if (spans.length == 1) { + child = spans.first; + } else { + child = TextSpan(children: spans); + } + + // Add the new text widget to the list + if (selectable) { + mergedTexts.add(SelectableText.rich( + TextSpan(children: spans), + textScaler: styleSheet.textScaler, + textAlign: textAlign ?? TextAlign.start, + onTap: onTapText, + )); + } else { + mergedTexts.add(Text.rich( + child, + textScaler: styleSheet.textScaler, + textAlign: textAlign ?? TextAlign.start, + )); } - final TextSpan? mergedSpan = _mergeSimilarTextSpans(children); - mergedTexts.add( - _buildRichText( - mergedSpan, - textAlign: textAlign, - ), - ); } else { + // If no text spans were found, add the current widget to the list mergedTexts.add(child); } } + return mergedTexts; } @@ -830,19 +904,30 @@ class MarkdownBuilder implements md.NodeVisitor { } /// Combine text spans with equivalent properties into a single span. - TextSpan? _mergeSimilarTextSpans(List? textSpans) { - if (textSpans == null || textSpans.length < 2) { - return TextSpan(children: textSpans); + List _mergeSimilarTextSpans(List textSpans) { + if (textSpans.length < 2) { + return textSpans; } - final List mergedSpans = [textSpans.first]; + final List mergedSpans = []; for (int index = 1; index < textSpans.length; index++) { - final TextSpan nextChild = textSpans[index]; - if (nextChild.recognizer == mergedSpans.last.recognizer && - nextChild.semanticsLabel == mergedSpans.last.semanticsLabel && - nextChild.style == mergedSpans.last.style) { - final TextSpan previous = mergedSpans.removeLast(); + final InlineSpan previous = + mergedSpans.isEmpty ? textSpans.first : mergedSpans.removeLast(); + final InlineSpan nextChild = textSpans[index]; + + final bool previousIsTextSpan = previous is TextSpan; + final bool nextIsTextSpan = nextChild is TextSpan; + if (!previousIsTextSpan || !nextIsTextSpan) { + mergedSpans.addAll([previous, nextChild]); + continue; + } + + final bool matchStyle = nextChild.recognizer == previous.recognizer && + nextChild.semanticsLabel == previous.semanticsLabel && + nextChild.style == previous.style; + + if (matchStyle) { mergedSpans.add(TextSpan( text: previous.toPlainText() + nextChild.toPlainText(), recognizer: previous.recognizer, @@ -850,15 +935,13 @@ class MarkdownBuilder implements md.NodeVisitor { style: previous.style, )); } else { - mergedSpans.add(nextChild); + mergedSpans.addAll([previous, nextChild]); } } // When the mergered spans compress into a single TextSpan return just that // TextSpan, otherwise bundle the set of TextSpans under a single parent. - return mergedSpans.length == 1 - ? mergedSpans.first - : TextSpan(children: mergedSpans); + return mergedSpans; } Widget _buildRichText(TextSpan? text, {TextAlign? textAlign, String? key}) { @@ -867,15 +950,18 @@ class MarkdownBuilder implements md.NodeVisitor { if (selectable) { return SelectableText.rich( text!, - textScaleFactor: styleSheet.textScaleFactor, + textScaler: styleSheet.textScaler, textAlign: textAlign ?? TextAlign.start, + onSelectionChanged: + (TextSelection selection, SelectionChangedCause? cause) => + onSelectionChanged!(text.text, selection, cause), onTap: onTapText, key: k, ); } else { return Text.rich( text!, - textScaleFactor: styleSheet.textScaleFactor, + textScaler: styleSheet.textScaler, textAlign: textAlign ?? TextAlign.start, key: k, ); diff --git a/packages/flutter_markdown/lib/src/style_sheet.dart b/packages/flutter_markdown/lib/src/style_sheet.dart index 8d73d318aa9c..7fc16d9881e0 100644 --- a/packages/flutter_markdown/lib/src/style_sheet.dart +++ b/packages/flutter_markdown/lib/src/style_sheet.dart @@ -59,8 +59,19 @@ class MarkdownStyleSheet { this.orderedListAlign = WrapAlignment.start, this.blockquoteAlign = WrapAlignment.start, this.codeblockAlign = WrapAlignment.start, - this.textScaleFactor, - }) : _styles = { + @Deprecated('Use textScaler instead.') this.textScaleFactor, + TextScaler? textScaler, + }) : assert( + textScaler == null || textScaleFactor == null, + 'textScaleFactor is deprecated and cannot be specified when textScaler is specified.', + ), + textScaler = textScaler ?? + // Internally, only textScaler is used, so convert the scale factor + // to a linear scaler. + (textScaleFactor == null + ? null + : TextScaler.linear(textScaleFactor)), + _styles = { 'a': a, 'p': p, 'li': p, @@ -380,8 +391,19 @@ class MarkdownStyleSheet { WrapAlignment? orderedListAlign, WrapAlignment? blockquoteAlign, WrapAlignment? codeblockAlign, - double? textScaleFactor, + @Deprecated('Use textScaler instead.') double? textScaleFactor, + TextScaler? textScaler, }) { + assert( + textScaler == null || textScaleFactor == null, + 'textScaleFactor is deprecated and cannot be specified when textScaler is specified.', + ); + // If either of textScaler or textScaleFactor is non-null, pass null for the + // other instead of the previous value, since only one is allowed. + final TextScaler? newTextScaler = + textScaler ?? (textScaleFactor == null ? this.textScaler : null); + final double? nextTextScaleFactor = + textScaleFactor ?? (textScaler == null ? this.textScaleFactor : null); return MarkdownStyleSheet( a: a ?? this.a, p: p ?? this.p, @@ -435,7 +457,8 @@ class MarkdownStyleSheet { orderedListAlign: orderedListAlign ?? this.orderedListAlign, blockquoteAlign: blockquoteAlign ?? this.blockquoteAlign, codeblockAlign: codeblockAlign ?? this.codeblockAlign, - textScaleFactor: textScaleFactor ?? this.textScaleFactor, + textScaler: newTextScaler, + textScaleFactor: nextTextScaleFactor, ); } @@ -497,6 +520,11 @@ class MarkdownStyleSheet { blockquoteAlign: other.blockquoteAlign, codeblockAlign: other.codeblockAlign, textScaleFactor: other.textScaleFactor, + // Only one of textScaler and textScaleFactor can be passed. If + // other.textScaleFactor is non-null, then the sheet was created with a + // textScaleFactor and the textScaler was derived from that, so should be + // ignored so that the textScaleFactor continues to be set. + textScaler: other.textScaleFactor == null ? other.textScaler : null, ); } @@ -650,7 +678,14 @@ class MarkdownStyleSheet { /// The [WrapAlignment] to use for a code block. Defaults to start. final WrapAlignment codeblockAlign; - /// The text scale factor to use in textual elements + /// The text scaler to use in textual elements. + final TextScaler? textScaler; + + /// The text scale factor to use in textual elements. + /// + /// This will be non-null only if the sheet was created with the deprecated + /// [textScaleFactor] instead of [textScaler]. + @Deprecated('Use textScaler instead.') final double? textScaleFactor; /// A [Map] from element name to the corresponding [TextStyle] object. @@ -717,7 +752,7 @@ class MarkdownStyleSheet { other.orderedListAlign == orderedListAlign && other.blockquoteAlign == blockquoteAlign && other.codeblockAlign == codeblockAlign && - other.textScaleFactor == textScaleFactor; + other.textScaler == textScaler; } @override @@ -774,6 +809,7 @@ class MarkdownStyleSheet { orderedListAlign, blockquoteAlign, codeblockAlign, + textScaler, textScaleFactor, ]); } diff --git a/packages/flutter_markdown/lib/src/widget.dart b/packages/flutter_markdown/lib/src/widget.dart index 3feee6b70f57..38ffbdcc32ef 100644 --- a/packages/flutter_markdown/lib/src/widget.dart +++ b/packages/flutter_markdown/lib/src/widget.dart @@ -12,6 +12,18 @@ import 'package:markdown/markdown.dart' as md; import '../flutter_markdown.dart'; import '_functions_io.dart' if (dart.library.html) '_functions_web.dart'; +/// Signature for callbacks used by [MarkdownWidget] when +/// [MarkdownWidget.selectable] is set to true and the user changes selection. +/// +/// The callback will return the entire block of text available for selection, +/// along with the current [selection] and the [cause] of the selection change. +/// This is a wrapper of [SelectionChangedCallback] with additional context +/// [text] for the caller to process. +/// +/// Used by [MarkdownWidget.onSelectionChanged] +typedef MarkdownOnSelectionChangedCallback = void Function( + String? text, TextSelection selection, SelectionChangedCause? cause); + /// Signature for callbacks used by [MarkdownWidget] when the user taps a link. /// The callback will return the link text, destination, and title from the /// Markdown link tag in the document. @@ -58,6 +70,11 @@ abstract class SyntaxHighlighter { /// An interface for an element builder. abstract class MarkdownElementBuilder { + /// For block syntax has to return true. + /// + /// By default returns false. + bool isBlockElement() => false; + /// Called when an Element has been reached, before its children have been /// visited. void visitElementBefore(md.Element element) {} @@ -173,6 +190,7 @@ abstract class MarkdownWidget extends StatefulWidget { this.styleSheet, this.styleSheetTheme = MarkdownStyleSheetBaseTheme.material, this.syntaxHighlighter, + this.onSelectionChanged, this.onTapLink, this.onTapText, this.imageDirectory, @@ -216,6 +234,9 @@ abstract class MarkdownWidget extends StatefulWidget { /// Called when the user taps a link. final MarkdownTapLinkCallback? onTapLink; + /// Called when the user changes selection when [selectable] is set to true. + final MarkdownOnSelectionChangedCallback? onSelectionChanged; + /// Default tap handler used when [selectable] is set to true final VoidCallback? onTapText; @@ -353,6 +374,7 @@ class _MarkdownWidgetState extends State paddingBuilders: widget.paddingBuilders, fitContent: widget.fitContent, listItemCrossAxisAlignment: widget.listItemCrossAxisAlignment, + onSelectionChanged: widget.onSelectionChanged, onTapText: widget.onTapText, softLineBreak: widget.softLineBreak, ); @@ -415,6 +437,7 @@ class MarkdownBody extends MarkdownWidget { super.styleSheet, super.styleSheetTheme = null, super.syntaxHighlighter, + super.onSelectionChanged, super.onTapLink, super.onTapText, super.imageDirectory, @@ -469,6 +492,7 @@ class Markdown extends MarkdownWidget { super.styleSheet, super.styleSheetTheme = null, super.syntaxHighlighter, + super.onSelectionChanged, super.onTapLink, super.onTapText, super.imageDirectory, diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index c3732a5c2cb8..c077bb64f93e 100644 --- a/packages/flutter_markdown/pubspec.yaml +++ b/packages/flutter_markdown/pubspec.yaml @@ -4,11 +4,11 @@ description: A Markdown renderer for Flutter. Create rich text output, formatted with simple Markdown tags. repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22 -version: 0.6.19 +version: 0.6.22 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: diff --git a/packages/flutter_markdown/test/all.dart b/packages/flutter_markdown/test/all.dart index 44387e0afe9c..28338dbdcf30 100644 --- a/packages/flutter_markdown/test/all.dart +++ b/packages/flutter_markdown/test/all.dart @@ -18,7 +18,7 @@ import 'selection_area_compatibility_test.dart' as selection_area_test; import 'style_sheet_test.dart' as style_sheet_test; import 'table_test.dart' as table_test; import 'text_alignment_test.dart' as text_alignment_test; -import 'text_scale_factor_test.dart' as text_scale_factor; +import 'text_scaler_test.dart' as text_scaler; import 'text_test.dart' as text_test; import 'uri_test.dart' as uri_test; @@ -40,6 +40,6 @@ void main() { table_test.defineTests(); text_test.defineTests(); text_alignment_test.defineTests(); - text_scale_factor.defineTests(); + text_scaler.defineTests(); uri_test.defineTests(); } diff --git a/packages/flutter_markdown/test/custom_syntax_test.dart b/packages/flutter_markdown/test/custom_syntax_test.dart index 1db01e3c0f9e..28d55cdfd0b5 100644 --- a/packages/flutter_markdown/test/custom_syntax_test.dart +++ b/packages/flutter_markdown/test/custom_syntax_test.dart @@ -35,6 +35,30 @@ void defineTests() { }, ); + testWidgets( + 'Custom block element', + (WidgetTester tester) async { + const String blockContent = 'note block'; + await tester.pumpWidget( + boilerplate( + Markdown( + data: '[!NOTE] $blockContent', + extensionSet: md.ExtensionSet.none, + blockSyntaxes: [NoteSyntax()], + builders: { + 'note': NoteBuilder(), + }, + ), + ), + ); + final ColoredBox container = + tester.widgetList(find.byType(ColoredBox)).first as ColoredBox; + expect(container.color, Colors.red); + expect(container.child, isInstanceOf()); + expect((container.child! as Text).data, blockContent); + }, + ); + testWidgets( 'link for wikistyle', (WidgetTester tester) async { @@ -77,9 +101,8 @@ void defineTests() { ); final Text textWidget = tester.widget(find.byType(Text)); - final TextSpan span = - (textWidget.textSpan! as TextSpan).children![0] as TextSpan; - final WidgetSpan widgetSpan = span.children![0] as WidgetSpan; + final TextSpan textSpan = textWidget.textSpan! as TextSpan; + final WidgetSpan widgetSpan = textSpan.children![0] as WidgetSpan; expect(widgetSpan.child, isInstanceOf()); }, ); @@ -133,10 +156,9 @@ void defineTests() { final TextSpan textSpan = textWidget.textSpan! as TextSpan; final TextSpan start = textSpan.children![0] as TextSpan; expect(start.text, 'this test replaces a string with a '); - final TextSpan end = textSpan.children![1] as TextSpan; - final TextSpan foo = end.children![0] as TextSpan; + final TextSpan foo = textSpan.children![1] as TextSpan; expect(foo.text, 'foo'); - final WidgetSpan widgetSpan = end.children![1] as WidgetSpan; + final WidgetSpan widgetSpan = textSpan.children![2] as WidgetSpan; expect(widgetSpan.child, isInstanceOf()); }, ); @@ -333,3 +355,28 @@ class ImgBuilder extends MarkdownElementBuilder { return Text('foo', style: preferredStyle); } } + +class NoteBuilder extends MarkdownElementBuilder { + @override + Widget? visitText(md.Text text, TextStyle? preferredStyle) { + return ColoredBox( + color: Colors.red, child: Text(text.text, style: preferredStyle)); + } + + @override + bool isBlockElement() { + return true; + } +} + +class NoteSyntax extends md.BlockSyntax { + @override + md.Node? parse(md.BlockParser parser) { + final md.Line line = parser.current; + parser.advance(); + return md.Element('note', [md.Text(line.content.substring(8))]); + } + + @override + RegExp get pattern => RegExp(r'^\[!NOTE] '); +} diff --git a/packages/flutter_markdown/test/inline_widget_test.dart b/packages/flutter_markdown/test/inline_widget_test.dart new file mode 100644 index 000000000000..d00102cc218a --- /dev/null +++ b/packages/flutter_markdown/test/inline_widget_test.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:markdown/markdown.dart' as md; + +import 'utils.dart'; + +void main() => defineTests(); + +void defineTests() { + group('InlineWidget', () { + testWidgets( + 'Test inline widget', + (WidgetTester tester) async { + await tester.pumpWidget( + boilerplate( + MarkdownBody( + data: 'Hello, foo bar', + builders: { + 'sub': SubscriptBuilder(), + }, + extensionSet: md.ExtensionSet( + [], + [SubscriptSyntax()], + ), + ), + ), + ); + + final Text textWidget = tester.firstWidget(find.byType(Text)); + final TextSpan span = textWidget.textSpan! as TextSpan; + + final TextSpan part1 = span.children![0] as TextSpan; + expect(part1.toPlainText(), 'Hello, '); + + final WidgetSpan part2 = span.children![1] as WidgetSpan; + expect(part2.alignment, PlaceholderAlignment.middle); + expect(part2.child, isA()); + expect((part2.child as Text).data, 'foo'); + + final TextSpan part3 = span.children![2] as TextSpan; + expect(part3.toPlainText(), ' bar'); + }, + ); + }); +} + +class SubscriptBuilder extends MarkdownElementBuilder { + @override + Widget visitElementAfterWithContext( + BuildContext context, + md.Element element, + TextStyle? preferredStyle, + TextStyle? parentStyle, + ) { + return Text.rich(WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Text(element.textContent), + )); + } +} + +class SubscriptSyntax extends md.InlineSyntax { + SubscriptSyntax() : super(_pattern); + + static const String _pattern = r'(foo)'; + + @override + bool onMatch(md.InlineParser parser, Match match) { + parser.addNode(md.Element.text('sub', match[1]!)); + return true; + } +} diff --git a/packages/flutter_markdown/test/style_sheet_test.dart b/packages/flutter_markdown/test/style_sheet_test.dart index ad8c1f0c695b..b48465a415a0 100644 --- a/packages/flutter_markdown/test/style_sheet_test.dart +++ b/packages/flutter_markdown/test/style_sheet_test.dart @@ -398,5 +398,65 @@ void defineTests() { ); }, ); + + testWidgets( + 'deprecated textScaleFactor is converted to linear scaler', + (WidgetTester tester) async { + const double scaleFactor = 2.0; + final MarkdownStyleSheet style = MarkdownStyleSheet( + textScaleFactor: scaleFactor, + ); + + expect(style.textScaler, const TextScaler.linear(scaleFactor)); + expect(style.textScaleFactor, scaleFactor); + }, + ); + + testWidgets( + 'deprecated textScaleFactor is null when a scaler is provided', + (WidgetTester tester) async { + const TextScaler scaler = TextScaler.linear(2.0); + final MarkdownStyleSheet style = MarkdownStyleSheet( + textScaler: scaler, + ); + + expect(style.textScaler, scaler); + expect(style.textScaleFactor, null); + }, + ); + + testWidgets( + 'copyWith textScaler overwrites both textScaler and textScaleFactor', + (WidgetTester tester) async { + final MarkdownStyleSheet original = MarkdownStyleSheet( + textScaleFactor: 2.0, + ); + + const TextScaler newScaler = TextScaler.linear(3.0); + final MarkdownStyleSheet copy = original.copyWith( + textScaler: newScaler, + ); + + expect(copy.textScaler, newScaler); + expect(copy.textScaleFactor, null); + }, + ); + + testWidgets( + 'copyWith textScaleFactor overwrites both textScaler and textScaleFactor', + (WidgetTester tester) async { + final MarkdownStyleSheet original = MarkdownStyleSheet( + textScaleFactor: 2.0, + ); + + const double newScaleFactor = 3.0; + final MarkdownStyleSheet copy = original.copyWith( + textScaleFactor: newScaleFactor, + ); + + expect(copy.textScaler, const TextScaler.linear(newScaleFactor)); + expect(copy.textScaleFactor, newScaleFactor); + }, + ); }); } diff --git a/packages/flutter_markdown/test/text_scale_factor_test.dart b/packages/flutter_markdown/test/text_scaler_test.dart similarity index 69% rename from packages/flutter_markdown/test/text_scale_factor_test.dart rename to packages/flutter_markdown/test/text_scaler_test.dart index 3710b3e0a62e..bc3ff911ef4e 100644 --- a/packages/flutter_markdown/test/text_scale_factor_test.dart +++ b/packages/flutter_markdown/test/text_scaler_test.dart @@ -10,33 +10,35 @@ import 'utils.dart'; void main() => defineTests(); void defineTests() { - group('Text Scale Factor', () { + group('Text Scaler', () { testWidgets( - 'should use style textScaleFactor in RichText', + 'should use style textScaler in RichText', (WidgetTester tester) async { + const TextScaler scaler = TextScaler.linear(2.0); const String data = 'Hello'; await tester.pumpWidget( boilerplate( MarkdownBody( - styleSheet: MarkdownStyleSheet(textScaleFactor: 2.0), + styleSheet: MarkdownStyleSheet(textScaler: scaler), data: data, ), ), ); final RichText richText = tester.widget(find.byType(RichText)); - expect(richText.textScaleFactor, 2.0); + expect(richText.textScaler, scaler); }, ); testWidgets( - 'should use MediaQuery textScaleFactor in RichText', + 'should use MediaQuery textScaler in RichText', (WidgetTester tester) async { + const TextScaler scaler = TextScaler.linear(2.0); const String data = 'Hello'; await tester.pumpWidget( boilerplate( const MediaQuery( - data: MediaQueryData(textScaleFactor: 2.0), + data: MediaQueryData(textScaler: scaler), child: MarkdownBody( data: data, ), @@ -45,18 +47,19 @@ void defineTests() { ); final RichText richText = tester.widget(find.byType(RichText)); - expect(richText.textScaleFactor, 2.0); + expect(richText.textScaler, scaler); }, ); testWidgets( - 'should use MediaQuery textScaleFactor in SelectableText.rich', + 'should use MediaQuery textScaler in SelectableText.rich', (WidgetTester tester) async { + const TextScaler scaler = TextScaler.linear(2.0); const String data = 'Hello'; await tester.pumpWidget( boilerplate( const MediaQuery( - data: MediaQueryData(textScaleFactor: 2.0), + data: MediaQueryData(textScaler: scaler), child: MarkdownBody( data: data, selectable: true, @@ -67,7 +70,7 @@ void defineTests() { final SelectableText selectableText = tester.widget(find.byType(SelectableText)); - expect(selectableText.textScaleFactor, 2.0); + expect(selectableText.textScaler, scaler); }, ); }); diff --git a/packages/flutter_markdown/test/text_test.dart b/packages/flutter_markdown/test/text_test.dart index cb4610c3f5cc..27f16cc90044 100644 --- a/packages/flutter_markdown/test/text_test.dart +++ b/packages/flutter_markdown/test/text_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -282,6 +283,62 @@ void defineTests() { expect(textTapResults == 'Text has been tapped.', true); }, ); + + testWidgets( + 'header with line of text and onSelectionChanged callback', + (WidgetTester tester) async { + const String data = '# abc def ghi\njkl opq'; + String? selectableText; + String? selectedText; + void onSelectionChanged(String? text, TextSelection selection, + SelectionChangedCause? cause) { + selectableText = text; + selectedText = text != null ? selection.textInside(text) : null; + } + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: MarkdownBody( + data: data, + selectable: true, + onSelectionChanged: onSelectionChanged, + ), + ), + ), + ); + + // Find the positions before character 'd' and 'f'. + final Offset dPos = positionInRenderedText(tester, 'abc def ghi', 4); + final Offset fPos = positionInRenderedText(tester, 'abc def ghi', 6); + // Select from 'd' until 'f'. + final TestGesture firstGesture = + await tester.startGesture(dPos, kind: PointerDeviceKind.mouse); + addTearDown(firstGesture.removePointer); + await tester.pump(); + await firstGesture.moveTo(fPos); + await firstGesture.up(); + await tester.pump(); + + expect(selectableText, 'abc def ghi'); + expect(selectedText, 'de'); + + // Find the positions before character 'j' and 'o'. + final Offset jPos = positionInRenderedText(tester, 'jkl opq', 0); + final Offset oPos = positionInRenderedText(tester, 'jkl opq', 4); + // Select from 'j' until 'o'. + final TestGesture secondGesture = + await tester.startGesture(jPos, kind: PointerDeviceKind.mouse); + addTearDown(secondGesture.removePointer); + await tester.pump(); + await secondGesture.moveTo(oPos); + await secondGesture.up(); + await tester.pump(); + + expect(selectableText, 'jkl opq'); + expect(selectedText, 'jkl '); + }, + ); }); group('Strikethrough', () { diff --git a/packages/flutter_markdown/test/utils.dart b/packages/flutter_markdown/test/utils.dart index 1cd902c30022..fa635f27c706 100644 --- a/packages/flutter_markdown/test/utils.dart +++ b/packages/flutter_markdown/test/utils.dart @@ -5,8 +5,10 @@ import 'dart:convert'; import 'dart:io' as io; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -25,6 +27,50 @@ Iterable selfAndDescendantWidgetsOf(Finder start, WidgetTester tester) { ]; } +// Returns the RenderEditable displaying the given text. +RenderEditable findRenderEditableWithText(WidgetTester tester, String text) { + final Iterable roots = + tester.renderObjectList(find.byType(EditableText)); + expect(roots, isNotEmpty); + + late RenderEditable renderEditable; + void recursiveFinder(RenderObject child) { + if (child is RenderEditable && child.plainText == text) { + renderEditable = child; + return; + } + child.visitChildren(recursiveFinder); + } + + for (final RenderObject root in roots) { + root.visitChildren(recursiveFinder); + } + + expect(renderEditable, isNotNull); + return renderEditable; +} + +// Returns the [textOffset] position in rendered [text]. +Offset positionInRenderedText( + WidgetTester tester, String text, int textOffset) { + final RenderEditable renderEditable = + findRenderEditableWithText(tester, text); + final Iterable textOffsetPoints = + renderEditable.getEndpointsForSelection( + TextSelection.collapsed(offset: textOffset), + ); + // Map the points to global positions. + final List endpoints = + textOffsetPoints.map((TextSelectionPoint point) { + return TextSelectionPoint( + renderEditable.localToGlobal(point.point), + point.direction, + ); + }).toList(); + expect(endpoints.length, 1); + return endpoints[0].point + const Offset(kIsWeb ? 1.0 : 0.0, -2.0); +} + void expectWidgetTypes(Iterable widgets, List expected) { final List actual = widgets.map((Widget w) => w.runtimeType).toList(); expect(actual, expected); @@ -169,7 +215,7 @@ void expectLinkTap(MarkdownLink? actual, MarkdownLink expected) { } String dumpRenderView() { - return WidgetsBinding.instance.renderViewElement!.toStringDeep().replaceAll( + return WidgetsBinding.instance.rootElement!.toStringDeep().replaceAll( RegExp(r'SliverChildListDelegate#\d+', multiLine: true), 'SliverChildListDelegate', ); diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 3b01fb1dbb49..d268648720c3 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT +## 13.2.2 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +- Fixes restoreRouteInformation issue when GoRouter.optionURLReflectsImperativeAPIs is true and the last match is ShellRouteMatch + +## 13.2.1 + +- Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. +- Fixes memory leaks. ## 13.2.0 diff --git a/packages/go_router/example/pubspec.yaml b/packages/go_router/example/pubspec.yaml index 5eb27e8536b3..eb4fd432ea46 100644 --- a/packages/go_router/example/pubspec.yaml +++ b/packages/go_router/example/pubspec.yaml @@ -4,8 +4,8 @@ version: 3.0.1 publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: adaptive_navigation: ^0.0.4 diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart index bcd79c48c136..49b6372c6933 100644 --- a/packages/go_router/lib/src/builder.dart +++ b/packages/go_router/lib/src/builder.dart @@ -189,6 +189,13 @@ class _CustomNavigatorState extends State<_CustomNavigator> { _pages = null; } + @override + void dispose() { + _controller?.dispose(); + _registry.dispose(); + super.dispose(); + } + void _updatePages(BuildContext context) { assert(_pages == null); final List> pages = >[]; diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 4ac4f03423c7..9cd92c1848b6 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -125,18 +125,27 @@ class GoRouteInformationParser extends RouteInformationParser { if (configuration.isEmpty) { return null; } - final String location; + String? location; if (GoRouter.optionURLReflectsImperativeAPIs && - configuration.matches.last is ImperativeRouteMatch) { - location = (configuration.matches.last as ImperativeRouteMatch) - .matches - .uri - .toString(); - } else { - location = configuration.uri.toString(); + (configuration.matches.last is ImperativeRouteMatch || + configuration.matches.last is ShellRouteMatch)) { + RouteMatchBase route = configuration.matches.last; + + while (route is! ImperativeRouteMatch) { + if (route is ShellRouteMatch && route.matches.isNotEmpty) { + route = route.matches.last; + } else { + break; + } + } + + if (route case final ImperativeRouteMatch safeRoute) { + location = safeRoute.matches.uri.toString(); + } } + return RouteInformation( - uri: Uri.parse(location), + uri: Uri.parse(location ?? configuration.uri.toString()), state: _routeMatchListCodec.encode(configuration), ); } diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index b93509a97adf..ac7945e62ece 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,13 +1,13 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 13.2.0 +version: 13.2.2 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" dependencies: collection: ^1.15.0 diff --git a/packages/go_router/test/builder_test.dart b/packages/go_router/test/builder_test.dart index f03c090a7b44..7b260d5250ea 100644 --- a/packages/go_router/test/builder_test.dart +++ b/packages/go_router/test/builder_test.dart @@ -371,6 +371,7 @@ void main() { ), ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: goRouter, diff --git a/packages/go_router/test/custom_transition_page_test.dart b/packages/go_router/test/custom_transition_page_test.dart index c0dac88d0cfb..710d9d19c673 100644 --- a/packages/go_router/test/custom_transition_page_test.dart +++ b/packages/go_router/test/custom_transition_page_test.dart @@ -22,6 +22,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -35,6 +36,7 @@ void main() { (WidgetTester tester) async { final ValueNotifier showHomeValueNotifier = ValueNotifier(false); + addTearDown(showHomeValueNotifier.dispose); await tester.pumpWidget( MaterialApp( home: ValueListenableBuilder( @@ -83,6 +85,7 @@ void main() { testWidgets('NoTransitionPage does not apply any reverse transition', (WidgetTester tester) async { final ValueNotifier showHomeValueNotifier = ValueNotifier(true); + addTearDown(showHomeValueNotifier.dispose); await tester.pumpWidget( MaterialApp( home: ValueListenableBuilder( @@ -139,6 +142,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: router)); expect(find.byKey(homeKey), findsOneWidget); router.push('/dismissible-modal'); @@ -176,6 +180,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: router)); expect(find.byKey(homeKey), findsOneWidget); diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index 9b7a99f13fd5..6513a1c9665f 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -12,6 +12,7 @@ import 'test_helpers.dart'; Future createGoRouter( WidgetTester tester, { Listenable? refreshListenable, + bool dispose = true, }) async { final GoRouter router = GoRouter( initialLocation: '/', @@ -25,6 +26,9 @@ Future createGoRouter( ], refreshListenable: refreshListenable, ); + if (dispose) { + addTearDown(router.dispose); + } await tester.pumpWidget(MaterialApp.router( routerConfig: router, )); @@ -65,6 +69,7 @@ Future createGoRouterWithStatefulShellRoute( ], builder: mockStackedShellBuilder), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: router, )); @@ -287,6 +292,7 @@ void main() { GoRoute(path: '/page-1', builder: (_, __) => const SizedBox()), ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: goRouter, @@ -369,6 +375,7 @@ void main() { builder: (_, __) => const SizedBox()), ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: goRouter, @@ -418,6 +425,7 @@ void main() { GoRoute(path: '/page-1', builder: (_, __) => const SizedBox()), ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: goRouter, @@ -535,6 +543,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: router, )); @@ -634,8 +643,13 @@ void main() { testWidgets('dispose unsubscribes from refreshListenable', (WidgetTester tester) async { final FakeRefreshListenable refreshListenable = FakeRefreshListenable(); - final GoRouter goRouter = - await createGoRouter(tester, refreshListenable: refreshListenable); + addTearDown(refreshListenable.dispose); + + final GoRouter goRouter = await createGoRouter( + tester, + refreshListenable: refreshListenable, + dispose: false, + ); await tester.pumpWidget(Container()); goRouter.dispose(); expect(refreshListenable.unsubscribed, true); diff --git a/packages/go_router/test/extension_test.dart b/packages/go_router/test/extension_test.dart index 97e5401bad6e..0f314edbf66e 100644 --- a/packages/go_router/test/extension_test.dart +++ b/packages/go_router/test/extension_test.dart @@ -26,6 +26,7 @@ void main() { builder: (_, __) => const SizedBox()) ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: router, )); diff --git a/packages/go_router/test/extra_codec_test.dart b/packages/go_router/test/extra_codec_test.dart index 3c858cad17d8..35291db978d8 100644 --- a/packages/go_router/test/extra_codec_test.dart +++ b/packages/go_router/test/extra_codec_test.dart @@ -32,6 +32,8 @@ void main() { return null; }, ); + + addTearDown(router.dispose); final SimpleDependency dependency = SimpleDependency(); addTearDown(() => dependency.dispose()); diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index 852e7d9fec6f..57d8612c0767 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -968,6 +968,8 @@ void main() { testWidgets('does not crash when inherited widget changes', (WidgetTester tester) async { final ValueNotifier notifier = ValueNotifier('initial'); + + addTearDown(notifier.dispose); final List routes = [ GoRoute( path: '/', @@ -985,6 +987,7 @@ void main() { final GoRouter router = GoRouter( routes: routes, ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -1108,6 +1111,47 @@ void main() { log.clear(); }); + testWidgets( + 'on push shell route with optionURLReflectImperativeAPIs = true', + (WidgetTester tester) async { + GoRouter.optionURLReflectsImperativeAPIs = true; + final List routes = [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + routes: [ + ShellRoute( + builder: + (BuildContext context, GoRouterState state, Widget child) => + child, + routes: [ + GoRoute( + path: 'c', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ) + ], + ), + ], + ), + ]; + + final GoRouter router = await createRouter(routes, tester); + + log.clear(); + router.push('/c?foo=bar'); + final RouteMatchListCodec codec = + RouteMatchListCodec(router.configuration); + await tester.pumpAndSettle(); + expect(log, [ + isMethodCall('selectMultiEntryHistory', arguments: null), + IsRouteUpdateCall('/c?foo=bar', false, + codec.encode(router.routerDelegate.currentConfiguration)), + ]); + GoRouter.optionURLReflectsImperativeAPIs = false; + }); + testWidgets('on push with optionURLReflectImperativeAPIs = true', (WidgetTester tester) async { GoRouter.optionURLReflectsImperativeAPIs = true; @@ -3212,6 +3256,7 @@ void main() { (WidgetTester tester) async { final GoRouterNamedLocationSpy router = GoRouterNamedLocationSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3230,6 +3275,7 @@ void main() { testWidgets('calls [go] on closest GoRouter', (WidgetTester tester) async { final GoRouterGoSpy router = GoRouterGoSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3247,6 +3293,7 @@ void main() { testWidgets('calls [goNamed] on closest GoRouter', (WidgetTester tester) async { final GoRouterGoNamedSpy router = GoRouterGoNamedSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3268,6 +3315,7 @@ void main() { testWidgets('calls [push] on closest GoRouter', (WidgetTester tester) async { final GoRouterPushSpy router = GoRouterPushSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3285,6 +3333,7 @@ void main() { testWidgets('calls [push] on closest GoRouter and waits for result', (WidgetTester tester) async { final GoRouterPushSpy router = GoRouterPushSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routeInformationProvider: router.routeInformationProvider, @@ -3305,6 +3354,7 @@ void main() { testWidgets('calls [pushNamed] on closest GoRouter', (WidgetTester tester) async { final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3326,6 +3376,7 @@ void main() { testWidgets('calls [pushNamed] on closest GoRouter and waits for result', (WidgetTester tester) async { final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routeInformationProvider: router.routeInformationProvider, @@ -3349,6 +3400,7 @@ void main() { testWidgets('calls [pop] on closest GoRouter', (WidgetTester tester) async { final GoRouterPopSpy router = GoRouterPopSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -3363,6 +3415,7 @@ void main() { testWidgets('calls [pop] on closest GoRouter with result', (WidgetTester tester) async { final GoRouterPopSpy router = GoRouterPopSpy(routes: routes); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( routerConfig: router, @@ -4315,6 +4368,7 @@ void main() { GoRoute(path: '/a', builder: (_, __) => const DummyScreen()), ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( @@ -4377,6 +4431,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( @@ -4443,6 +4498,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( @@ -4499,6 +4555,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: router)); @@ -4568,6 +4625,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( @@ -4638,6 +4696,7 @@ void main() { ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: router)); diff --git a/packages/go_router/test/helpers/error_screen_helpers.dart b/packages/go_router/test/helpers/error_screen_helpers.dart index 26822e27333c..56f03e9fd38d 100644 --- a/packages/go_router/test/helpers/error_screen_helpers.dart +++ b/packages/go_router/test/helpers/error_screen_helpers.dart @@ -42,6 +42,7 @@ WidgetTesterCallback testClickingTheButtonRedirectsToRoot({ ), ], ); + addTearDown(router.dispose); await tester.pumpWidget(appRouterBuilder(router)); await tester.tap(buttonFinder); await tester.pumpAndSettle(); diff --git a/packages/go_router/test/inherited_test.dart b/packages/go_router/test/inherited_test.dart index 6c6d965ceb34..a5902d47dfee 100644 --- a/packages/go_router/test/inherited_test.dart +++ b/packages/go_router/test/inherited_test.dart @@ -97,6 +97,7 @@ void main() { ) ], ); + addTearDown(router.dispose); await tester.pumpWidget( MaterialApp.router( diff --git a/packages/go_router/test/name_case_test.dart b/packages/go_router/test/name_case_test.dart index 6e3f067197fe..d2323c346533 100644 --- a/packages/go_router/test/name_case_test.dart +++ b/packages/go_router/test/name_case_test.dart @@ -25,6 +25,7 @@ void main() { ), ], ); + addTearDown(router.dispose); // run MaterialApp, initial screen path is '/' -> ScreenA await tester.pumpWidget( diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart index 47ea84c4f940..2d42de859e7e 100644 --- a/packages/go_router/test/parser_test.dart +++ b/packages/go_router/test/parser_test.dart @@ -27,6 +27,7 @@ void main() { redirectLimit: redirectLimit, redirect: redirect, ); + addTearDown(router.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: router, )); diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index dc75fecd1899..1308d42635d1 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -199,6 +199,7 @@ void main() { initialLocation: '/build', routes: _routes, ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsOneWidget); expect(find.byKey(const Key('buildPage')), findsNothing); @@ -212,6 +213,7 @@ void main() { initialLocation: '/build-page', routes: _routes, ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsOneWidget); @@ -229,6 +231,7 @@ void main() { _shellRouteDataBuilder, ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsOneWidget); expect(find.byKey(const Key('page-builder')), findsNothing); @@ -272,6 +275,7 @@ void main() { ), ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router( routerConfig: goRouter, )); @@ -301,6 +305,7 @@ void main() { _shellRouteDataPageBuilder, ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsNothing); expect(find.byKey(const Key('page-builder')), findsOneWidget); @@ -318,6 +323,7 @@ void main() { _statefulShellRouteDataBuilder, ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsOneWidget); expect(find.byKey(const Key('page-builder')), findsNothing); @@ -333,6 +339,7 @@ void main() { _statefulShellRouteDataPageBuilder, ], ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('builder')), findsNothing); expect(find.byKey(const Key('page-builder')), findsOneWidget); @@ -367,6 +374,7 @@ void main() { initialLocation: '/redirect', routes: _routes, ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsOneWidget); @@ -380,6 +388,7 @@ void main() { initialLocation: '/redirect-with-state', routes: _routes, ); + addTearDown(goRouter.dispose); await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); expect(find.byKey(const Key('build')), findsNothing); expect(find.byKey(const Key('buildPage')), findsNothing); diff --git a/packages/go_router/test/routing_config_test.dart b/packages/go_router/test/routing_config_test.dart index da36885e0037..949c25934c66 100644 --- a/packages/go_router/test/routing_config_test.dart +++ b/packages/go_router/test/routing_config_test.dart @@ -18,6 +18,7 @@ void main() { redirect: (_, __) => '/', ), ); + addTearDown(config.dispose); final GoRouter router = await createRouterWithRoutingConfig(config, tester); expect(find.text('home'), findsOneWidget); @@ -35,6 +36,7 @@ void main() { ], ), ); + addTearDown(config.dispose); await createRouterWithRoutingConfig(config, tester); expect(find.text('home'), findsOneWidget); @@ -56,6 +58,7 @@ void main() { ], ), ); + addTearDown(config.dispose); final GoRouter router = await createRouterWithRoutingConfig( config, tester, @@ -87,6 +90,7 @@ void main() { ], ), ); + addTearDown(config.dispose); final GoRouter router = await createRouterWithRoutingConfig( config, tester, @@ -120,11 +124,13 @@ void main() { ], ), ); + addTearDown(config.dispose); final GoRouter router = await createRouterWithRoutingConfig( config, tester, errorBuilder: (_, __) => const Text('error'), ); + expect(find.text('home'), findsOneWidget); // Sanity check. router.goNamed('abc'); diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index 112617832001..e6f69c507885 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -188,6 +188,7 @@ Future createRouter( requestFocus: requestFocus, overridePlatformDefaultLocation: overridePlatformDefaultLocation, ); + addTearDown(goRouter.dispose); await tester.pumpWidget( MaterialApp.router( restorationScopeId: @@ -221,6 +222,7 @@ Future createRouterWithRoutingConfig( requestFocus: requestFocus, overridePlatformDefaultLocation: overridePlatformDefaultLocation, ); + addTearDown(goRouter.dispose); await tester.pumpWidget( MaterialApp.router( restorationScopeId: @@ -335,6 +337,12 @@ class DummyRestorableStatefulWidgetState } } + @override + void dispose() { + _counter.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) => Container(); } diff --git a/packages/google_identity_services_web/CHANGELOG.md b/packages/google_identity_services_web/CHANGELOG.md index 92c96c07bdce..d7ed98a3c8cd 100644 --- a/packages/google_identity_services_web/CHANGELOG.md +++ b/packages/google_identity_services_web/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.3.1+1 + +* Uses `TrustedTypes` from `web: ^0.5.1`. + +## 0.3.1 + +* Updates web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. + ## 0.3.0+2 * Adds `fedcm_auto` to `CredentialSelectBy` enum. diff --git a/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart index 77914b746740..1d1c5c29ea81 100644 --- a/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart +++ b/packages/google_identity_services_web/example/integration_test/js_interop_id_test.dart @@ -66,9 +66,7 @@ void main() async { expectConfigValue('login_uri', 'https://www.example.com/login'); expectConfigValue('native_callback', utils.isAJs('function')); expectConfigValue('cancel_on_tap_outside', isFalse); - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - expectConfigValue('allowed_parent_origin', isA()); + expectConfigValue('allowed_parent_origin', isA>()); expectConfigValue('prompt_parent_id', 'some_dom_id'); expectConfigValue('nonce', 's0m3_r4ndOM_vALu3'); expectConfigValue('context', 'signin'); diff --git a/packages/google_identity_services_web/example/pubspec.yaml b/packages/google_identity_services_web/example/pubspec.yaml index e53f85dff1cb..fa625a5edd69 100644 --- a/packages/google_identity_services_web/example/pubspec.yaml +++ b/packages/google_identity_services_web/example/pubspec.yaml @@ -1,11 +1,10 @@ name: google_identity_services_web_example description: An example for the google_identity_services_web package, OneTap. publish_to: 'none' -version: 0.0.1 environment: - flutter: ">=3.16.0" - sdk: ">=3.2.0 <4.0.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: @@ -13,7 +12,7 @@ dependencies: google_identity_services_web: path: ../ http: ">=0.13.0 <2.0.0" - web: ">=0.3.0 <0.5.0" + web: ^0.5.1 dev_dependencies: build_runner: ^2.1.10 # To extract README excerpts only. diff --git a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart index a02197e0cf83..8467854640e8 100644 --- a/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart +++ b/packages/google_identity_services_web/lib/src/js_interop/google_accounts_id.dart @@ -338,9 +338,7 @@ abstract class IdConfiguration { JSString? context, JSString? state_cookie_domain, JSString? ux_mode, - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - JSArray? allowed_parent_origin, + JSArray? allowed_parent_origin, JSFunction? intermediate_iframe_close_callback, JSBoolean? itp_support, JSString? login_hint, diff --git a/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart b/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart index 76dc411c103e..ed55d5d81298 100644 --- a/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart +++ b/packages/google_identity_services_web/lib/src/js_interop/package_web_tweaks.dart @@ -2,34 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// Provides some useful tweaks to `package:web`. -library package_web_tweaks; - import 'dart:js_interop'; + import 'package:web/web.dart' as web; +// TODO(kevmoo): Make this file unnecessary, https://github.com/dart-lang/web/issues/175 + /// This extension gives web.window a nullable getter to the `trustedTypes` /// property, which needs to be used to check for feature support. extension NullableTrustedTypesGetter on web.Window { + /// (Nullable) Bindings to window.trustedTypes. + /// + /// This may be null if the browser doesn't support the Trusted Types API. /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API @JS('trustedTypes') external web.TrustedTypePolicyFactory? get nullableTrustedTypes; } -/// This extension allows a trusted type policy to create a script URL without -/// the `args` parameter (which in Chrome currently fails). -extension CreateScriptUrlWithoutArgs on web.TrustedTypePolicy { - /// +/// Allows setting a TrustedScriptURL as the src of a script element. +extension TrustedTypeSrcAttribute on web.HTMLScriptElement { + @JS('src') + external set trustedSrc(web.TrustedScriptURL value); +} + +/// Allows creating a script URL only from a string, with no arguments. +extension CreateScriptUrlNoArgs on web.TrustedTypePolicy { + /// Allows calling `createScriptURL` with only the `input` argument. @JS('createScriptURL') external web.TrustedScriptURL createScriptURLNoArgs( String input, ); } - -/// This extension allows setting a TrustedScriptURL as the src of a script element, -/// which currently only accepts a string. -extension TrustedTypeSrcAttribute on web.HTMLScriptElement { - /// - @JS('src') - external set srcTT(web.TrustedScriptURL value); -} diff --git a/packages/google_identity_services_web/lib/src/js_loader.dart b/packages/google_identity_services_web/lib/src/js_loader.dart index bd876f98c5ce..e32f59c802b1 100644 --- a/packages/google_identity_services_web/lib/src/js_loader.dart +++ b/packages/google_identity_services_web/lib/src/js_loader.dart @@ -47,7 +47,7 @@ Future loadWebSdk({ ..async = true ..defer = true; if (trustedUrl != null) { - script.srcTT = trustedUrl; + script.trustedSrc = trustedUrl; } else { script.src = _url; } diff --git a/packages/google_identity_services_web/pubspec.yaml b/packages/google_identity_services_web/pubspec.yaml index 2f1e5b913901..0c61580d79e2 100644 --- a/packages/google_identity_services_web/pubspec.yaml +++ b/packages/google_identity_services_web/pubspec.yaml @@ -2,14 +2,14 @@ name: google_identity_services_web description: A Dart JS-interop layer for Google Identity Services. Google's new sign-in SDK for Web that supports multiple types of credentials. repository: https://github.com/flutter/packages/tree/main/packages/google_identity_services_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_identiy_services_web%22 -version: 0.3.0+2 +version: 0.3.1+1 environment: - sdk: ">=3.2.0 <4.0.0" + sdk: ^3.3.0 dependencies: meta: ^1.3.0 - web: ">=0.3.0 <0.5.0" + web: ^0.5.1 dev_dependencies: path: ^1.8.1 diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 095e04709027..50ca4a9e6894 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT - -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +## 2.6.0 + +* Adds `style` to the GoogleMap widget constructor. This allows setting the map + style during creation, avoiding the possibility of the default style being + displayed briefly. +* Deprecates `GoogleMapController.setMapStyle` in favor of setting the style via + the new widget `style` parameter. +* Updates minimum supported SDK version to Flutter 3.19. ## 2.5.3 diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart index 01716232814e..3d99369e33be 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart @@ -448,6 +448,28 @@ void runTests() { await mapIdCompleter.future; }, ); + + testWidgets('getStyleError reports last error', (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await pumpMap( + tester, + GoogleMap( + key: key, + initialCameraPosition: kInitialCameraPosition, + style: '[[[this is an invalid style', + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + ); + + final GoogleMapController controller = await controllerCompleter.future; + final String? error = await controller.getStyleError(); + expect(error, isNotNull); + }); } /// Repeatedly checks an asynchronous value against a test condition. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart index 5348c1001af7..26c0b2ab720a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart @@ -4,6 +4,7 @@ // ignore_for_file: public_member_api_docs +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -59,6 +60,7 @@ class MapUiBodyState extends State { bool _myLocationButtonEnabled = true; late GoogleMapController _controller; bool _nightMode = false; + String _mapStyle = ''; @override void initState() { @@ -243,27 +245,18 @@ class MapUiBodyState extends State { return rootBundle.loadString(path); } - void _setMapStyle(String mapStyle) { - setState(() { - _nightMode = true; - _controller.setMapStyle(mapStyle); - }); - } - // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), - onPressed: () { - if (_nightMode) { - setState(() { - _nightMode = false; - _controller.setMapStyle(null); - }); - } else { - _getFileData('assets/night_mode.json').then(_setMapStyle); - } + onPressed: () async { + _nightMode = !_nightMode; + final String style = + _nightMode ? await _getFileData('assets/night_mode.json') : ''; + setState(() { + _mapStyle = style; + }); }, ); } @@ -278,6 +271,7 @@ class MapUiBodyState extends State { cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, + style: _mapStyle, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, @@ -352,5 +346,13 @@ class MapUiBodyState extends State { _controller = controller; _isMapCreated = true; }); + // Log any style errors to the console for debugging. + if (kDebugMode) { + _controller.getStyleError().then((String? error) { + if (error != null) { + debugPrint(error); + } + }); + } } } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index efed14ceecbf..ba306fd8bd9e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ google_maps_flutter_android: ^2.5.0 - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 095d15867acc..393288a6165f 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -190,11 +190,17 @@ class GoogleMapController { /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) /// style reference for more information regarding the supported styles. + @Deprecated('Use GoogleMap.style instead.') Future setMapStyle(String? mapStyle) { return GoogleMapsFlutterPlatform.instance .setMapStyle(mapStyle, mapId: mapId); } + /// Returns the last style error, if any. + Future getStyleError() { + return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + } + /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 3b73e55664dd..c50ffccfa634 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -91,6 +91,7 @@ class GoogleMap extends StatefulWidget { const GoogleMap({ super.key, required this.initialCameraPosition, + this.style, this.onMapCreated, this.gestureRecognizers = const >{}, this.webGestureHandling, @@ -136,6 +137,19 @@ class GoogleMap extends StatefulWidget { /// The initial position of the map's camera. final CameraPosition initialCameraPosition; + /// The style for the map. + /// + /// Set to null to clear any previous custom styling. + /// + /// If problems were detected with the [mapStyle], including un-parsable + /// styling JSON, unrecognized feature type, unrecognized element type, or + /// invalid styler keys, the style is left unchanged, and the error can be + /// retrieved with [GoogleMapController.getStyleError]. + /// + /// The style string can be generated using the + /// [map style tool](https://mapstyle.withgoogle.com/). + final String? style; + /// True if the map should show a compass when rotated. final bool compassEnabled; @@ -556,5 +570,8 @@ MapConfiguration _configurationFromMapWidget(GoogleMap map) { trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, cloudMapId: map.cloudMapId, + // A null style in the widget means no style, which is expressed as '' in + // the configuration to distinguish from no change (null). + style: map.style ?? '', ); } diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 9b53f596fdf6..598ab6f7aeb6 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.5.3 +version: 2.6.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -21,10 +21,10 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_android: ^2.5.0 - google_maps_flutter_ios: ^2.3.0 - google_maps_flutter_platform_interface: ^2.4.0 - google_maps_flutter_web: ^0.5.2 + google_maps_flutter_android: ^2.7.0 + google_maps_flutter_ios: ^2.5.0 + google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_web: ^0.5.6 dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart index 7005a8d3ab60..9276a7dbda30 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart @@ -551,4 +551,32 @@ void main() { expect(map.mapConfiguration.buildingsEnabled, true); }); + + testWidgets('Can update style', (WidgetTester tester) async { + const String initialStyle = '[]'; + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), + style: initialStyle, + ), + ), + ); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.mapConfiguration.style, initialStyle); + + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), + ), + ), + ); + + expect(map.mapConfiguration.style, ''); + }); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index ba2011f39be2..a099bb5ef424 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 2.7.0 +* Adds support for `MapConfiguration.style`. +* Adds support for `getStyleError`. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 78c7dc2428c6..b473f1ea17d8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -377,6 +377,10 @@ static void interpretGoogleMapOptions(Object o, GoogleMapOptionsSink sink) { if (buildingsEnabled != null) { sink.setBuildingsEnabled(toBoolean(buildingsEnabled)); } + final Object style = data.get("style"); + if (style != null) { + sink.setMapStyle(toString(style)); + } } /** Returns the dartMarkerId of the interpreted marker. */ diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java index fa82b77743d2..dfbaf02b9d4a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java @@ -6,6 +6,7 @@ import android.content.Context; import android.graphics.Rect; +import androidx.annotation.Nullable; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLngBounds; @@ -27,6 +28,7 @@ class GoogleMapBuilder implements GoogleMapOptionsSink { private Object initialCircles; private List> initialTileOverlays; private Rect padding = new Rect(0, 0, 0, 0); + private @Nullable String style; GoogleMapController build( int id, @@ -48,6 +50,7 @@ GoogleMapController build( controller.setInitialCircles(initialCircles); controller.setPadding(padding.top, padding.left, padding.bottom, padding.right); controller.setInitialTileOverlays(initialTileOverlays); + controller.setMapStyle(style); return controller; } @@ -178,4 +181,9 @@ public void setInitialCircles(Object initialCircles) { public void setInitialTileOverlays(List> initialTileOverlays) { this.initialTileOverlays = initialTileOverlays; } + + @Override + public void setMapStyle(@Nullable String style) { + this.style = style; + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 917d45218345..b4102471c4b2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -48,6 +48,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** Controller of a single GoogleMaps MapView instance. */ final class GoogleMapController @@ -87,6 +88,9 @@ final class GoogleMapController private List initialPolylines; private List initialCircles; private List> initialTileOverlays; + // Null except between initialization and onMapReady. + private @Nullable String initialMapStyle; + private @Nullable String lastStyleError; @VisibleForTesting List initialPadding; GoogleMapController( @@ -169,6 +173,10 @@ public void onMapReady(GoogleMap googleMap) { initialPadding.get(2), initialPadding.get(3)); } + if (initialMapStyle != null) { + updateMapStyle(initialMapStyle); + initialMapStyle = null; + } } // Returns the first TextureView found in the view hierarchy. @@ -459,26 +467,22 @@ public void onSnapshotReady(Bitmap bitmap) { } case "map#setStyle": { - boolean mapStyleSet; - if (call.arguments instanceof String) { - String mapStyle = (String) call.arguments; - if (mapStyle == null) { - mapStyleSet = googleMap.setMapStyle(null); - } else { - mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); - } - } else { - mapStyleSet = googleMap.setMapStyle(null); - } + Object arg = call.arguments; + final String style = arg instanceof String ? (String) arg : null; + final boolean mapStyleSet = updateMapStyle(style); ArrayList mapStyleResult = new ArrayList<>(2); mapStyleResult.add(mapStyleSet); if (!mapStyleSet) { - mapStyleResult.add( - "Unable to set the map style. Please check console logs for errors."); + mapStyleResult.add(lastStyleError); } result.success(mapStyleResult); break; } + case "map#getStyleError": + { + result.success(lastStyleError); + break; + } case "tileOverlays#update": { List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); @@ -926,4 +930,22 @@ public void setTrafficEnabled(boolean trafficEnabled) { public void setBuildingsEnabled(boolean buildingsEnabled) { this.buildingsEnabled = buildingsEnabled; } + + public void setMapStyle(@Nullable String style) { + if (googleMap == null) { + initialMapStyle = style; + } else { + updateMapStyle(style); + } + } + + private boolean updateMapStyle(String style) { + // Dart passes an empty string to indicate that the style should be cleared. + final MapStyleOptions mapStyleOptions = + style == null || style.isEmpty() ? null : new MapStyleOptions(style); + final boolean set = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions); + lastStyleError = + set ? null : "Unable to set the map style. Please check console logs for errors."; + return set; + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java index 17f0d970a4ef..95c550c92fd8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java @@ -4,6 +4,7 @@ package io.flutter.plugins.googlemaps; +import androidx.annotation.Nullable; import com.google.android.gms.maps.model.LatLngBounds; import java.util.List; import java.util.Map; @@ -55,4 +56,6 @@ interface GoogleMapOptionsSink { void setInitialCircles(Object initialCircles); void setInitialTileOverlays(List> initialTileOverlays); + + void setMapStyle(@Nullable String style); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart index 97e7dfa0a37e..e03fe8f8851b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart @@ -722,7 +722,8 @@ void googleMapsTests() { await controllerCompleter.future; const String mapStyle = '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]'; - await controller.setMapStyle(mapStyle); + await GoogleMapsFlutterPlatform.instance + .setMapStyle(mapStyle, mapId: controller.mapId); }); testWidgets('testSetMapStyle invalid Json String', @@ -746,10 +747,12 @@ void googleMapsTests() { await controllerCompleter.future; try { - await controller.setMapStyle('invalid_value'); + await GoogleMapsFlutterPlatform.instance + .setMapStyle('invalid_value', mapId: controller.mapId); fail('expected MapStyleException'); } on MapStyleException catch (e) { expect(e.cause, isNotNull); + expect(await controller.getStyleError(), isNotNull); } }); @@ -771,7 +774,8 @@ void googleMapsTests() { final ExampleGoogleMapController controller = await controllerCompleter.future; - await controller.setMapStyle(null); + await GoogleMapsFlutterPlatform.instance + .setMapStyle(null, mapId: controller.mapId); }); testWidgets('testGetLatLng', (WidgetTester tester) async { @@ -1211,6 +1215,58 @@ void googleMapsTests() { await mapIdCompleter.future; }, ); + + testWidgets('getStyleError reports last error', (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + style: '[[[this is an invalid style', + onMapCreated: (ExampleGoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final ExampleGoogleMapController controller = + await controllerCompleter.future; + String? error = await controller.getStyleError(); + for (int i = 0; i < 1000 && error == null; i++) { + await Future.delayed(const Duration(milliseconds: 10)); + error = await controller.getStyleError(); + } + expect(error, isNotNull); + }); + + testWidgets('getStyleError returns null for a valid style', + (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + // An empty array is the simplest valid style. + style: '[]', + onMapCreated: (ExampleGoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final ExampleGoogleMapController controller = + await controllerCompleter.future; + final String? error = await controller.getStyleError(); + expect(error, isNull); + }); } class _DebugTileProvider implements TileProvider { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart index f176b987e28b..0734731af7c3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart @@ -144,12 +144,6 @@ class ExampleGoogleMapController { .moveCamera(cameraUpdate, mapId: mapId); } - /// Sets the styling of the base map. - Future setMapStyle(String? mapStyle) { - return GoogleMapsFlutterPlatform.instance - .setMapStyle(mapStyle, mapId: mapId); - } - /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); @@ -195,6 +189,11 @@ class ExampleGoogleMapController { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } + /// Returns the last style error, if any. + Future getStyleError() { + return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + } + /// Disposes of the platform resources void dispose() { GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId); @@ -245,6 +244,7 @@ class ExampleGoogleMap extends StatefulWidget { this.onTap, this.onLongPress, this.cloudMapId, + this.style, }); /// Callback method for when the map is ready to be used. @@ -353,6 +353,9 @@ class ExampleGoogleMap extends StatefulWidget { /// for more details. final String? cloudMapId; + /// The locally configured style for the map. + final String? style; + /// Creates a [State] for this [ExampleGoogleMap]. @override State createState() => _ExampleGoogleMapState(); @@ -539,5 +542,6 @@ MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) { trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, cloudMapId: map.cloudMapId, + style: map.style, ); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart index 311e2267aa0c..105676da9ed5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart @@ -60,6 +60,7 @@ class MapUiBodyState extends State { bool _myLocationButtonEnabled = true; late ExampleGoogleMapController _controller; bool _nightMode = false; + String _mapStyle = ''; @override void initState() { @@ -244,27 +245,16 @@ class MapUiBodyState extends State { return rootBundle.loadString(path); } - void _setMapStyle(String mapStyle) { - setState(() { - _nightMode = true; - _controller.setMapStyle(mapStyle); - }); - } - - // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { - assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), - onPressed: () { - if (_nightMode) { - setState(() { - _nightMode = false; - _controller.setMapStyle(null); - }); - } else { - _getFileData('assets/night_mode.json').then(_setMapStyle); - } + onPressed: () async { + _nightMode = !_nightMode; + final String style = + _nightMode ? await _getFileData('assets/night_mode.json') : ''; + setState(() { + _mapStyle = style; + }); }, ); } @@ -279,6 +269,7 @@ class MapUiBodyState extends State { cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, + style: _mapStyle, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, @@ -353,5 +344,11 @@ class MapUiBodyState extends State { _controller = controller; _isMapCreated = true; }); + // Log any style errors to the console for debugging. + _controller.getStyleError().then((String? error) { + if (error != null) { + debugPrint(error); + } + }); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml index f241ddc6c920..533ca53c2914 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # 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: ../ - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 4cb87722f375..d40d795d6a32 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -484,6 +484,11 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return _channel(mapId).invokeMethod('map#takeSnapshot'); } + @override + Future getStyleError({required int mapId}) { + return _channel(mapId).invokeMethod('map#getStyleError'); + } + /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the /// Google Maps widget. /// @@ -727,6 +732,7 @@ Map _jsonForMapConfiguration(MapConfiguration config) { if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!, + if (config.style != null) 'style': config.style!, }; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index 30b1644e313c..1f2a3be877c1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.6.2 +version: 2.7.0 environment: sdk: ^3.1.0 @@ -21,7 +21,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 stream_transform: ^2.0.0 dev_dependencies: diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index 4eae3cfc1f0f..2d1f96e1db09 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,3 +1,16 @@ +## 2.5.2 + +* Fixes the tile overlay not correctly displaying on physical ios devices. + +## 2.5.1 + +* Makes the tile overlay callback invoke the platform channel on the platform thread. + +## 2.5.0 + +* Adds support for `MapConfiguration.style`. +* Adds support for `getStyleError`. + ## 2.4.2 * Fixes a bug in "takeSnapshot" function that incorrectly returns a blank image on iOS 17. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/integration_test/google_maps_test.dart index 9902456444d6..e49c451d08ee 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/integration_test/google_maps_test.dart @@ -583,7 +583,8 @@ void main() { await controllerCompleter.future; const String mapStyle = '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]'; - await controller.setMapStyle(mapStyle); + await GoogleMapsFlutterPlatform.instance + .setMapStyle(mapStyle, mapId: controller.mapId); }); testWidgets('testSetMapStyle invalid Json String', @@ -607,10 +608,12 @@ void main() { await controllerCompleter.future; try { - await controller.setMapStyle('invalid_value'); + await GoogleMapsFlutterPlatform.instance + .setMapStyle('invalid_value', mapId: controller.mapId); fail('expected MapStyleException'); } on MapStyleException catch (e) { expect(e.cause, isNotNull); + expect(await controller.getStyleError(), isNotNull); } }); @@ -632,7 +635,8 @@ void main() { final ExampleGoogleMapController controller = await controllerCompleter.future; - await controller.setMapStyle(null); + await GoogleMapsFlutterPlatform.instance + .setMapStyle(null, mapId: controller.mapId); }); testWidgets('testGetLatLng', (WidgetTester tester) async { @@ -1048,6 +1052,54 @@ void main() { ), )); }); + + testWidgets('getStyleError reports last error', (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + style: '[[[this is an invalid style', + onMapCreated: (ExampleGoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final ExampleGoogleMapController controller = + await controllerCompleter.future; + final String? error = await controller.getStyleError(); + expect(error, isNotNull); + }); + + testWidgets('getStyleError returns null for a valid style', + (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + // An empty array is the simplest valid style. + style: '[]', + onMapCreated: (ExampleGoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final ExampleGoogleMapController controller = + await controllerCompleter.future; + final String? error = await controller.getStyleError(); + expect(error, isNull); + }); } class _DebugTileProvider implements TileProvider { diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/project.pbxproj index 6ae6a4f3333d..432bdd7b15f6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0DD7B6C32B744EEF00E857FD /* FLTTileProviderControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD7B6C22B744EEF00E857FD /* FLTTileProviderControllerTests.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; @@ -54,6 +55,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0DD7B6C22B744EEF00E857FD /* FLTTileProviderControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTTileProviderControllerTests.m; sourceTree = ""; }; 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -201,6 +203,7 @@ 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, F7151F14265D7ED70028CB91 /* Info.plist */, + 0DD7B6C22B744EEF00E857FD /* FLTTileProviderControllerTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -460,6 +463,7 @@ F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */, 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */, 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */, + 0DD7B6C32B744EEF00E857FD /* FLTTileProviderControllerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e271ffe8da9a..f8d70602adf7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + +@interface FLTTileProviderControllerTests : XCTestCase +@end + +@implementation FLTTileProviderControllerTests + +- (void)testCallChannelOnPlatformThread { + id channel = OCMClassMock(FlutterMethodChannel.class); + FLTTileProviderController *controller = [[FLTTileProviderController alloc] init:channel + withTileOverlayIdentifier:@"foo"]; + XCTAssertNotNil(controller); + XCTestExpectation *expectation = [self expectationWithDescription:@"invokeMethod"]; + OCMStub([channel invokeMethod:[OCMArg any] arguments:[OCMArg any] result:[OCMArg any]]) + .andDo(^(NSInvocation *invocation) { + XCTAssertTrue([[NSThread currentThread] isMainThread]); + [expectation fulfill]; + }); + id receiver = OCMProtocolMock(@protocol(GMSTileReceiver)); + [controller requestTileForX:0 y:0 zoom:0 receiver:receiver]; + [self waitForExpectations:@[ expectation ] timeout:10.0]; +} + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/pubspec.yaml index e5e0cd2a4b1e..a0b172a65e1c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios12/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # 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: ../../ - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 maps_example_dart: path: ../shared/maps_example_dart/ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios13/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios13/pubspec.yaml index e5e0cd2a4b1e..a0b172a65e1c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios13/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios13/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # 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: ../../ - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 maps_example_dart: path: ../shared/maps_example_dart/ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj index 42fd3929e9c7..e8c5c23b19fa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; }; + F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; }; F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; }; FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; }; /* End PBXBuildFile section */ @@ -68,6 +69,7 @@ E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F269303A2BB389BF00BF17C4 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = ""; }; F7151F10265D7ED70028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsTests.m; sourceTree = ""; }; F7151F14265D7ED70028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -174,6 +176,7 @@ F7151F11265D7ED70028CB91 /* RunnerTests */ = { isa = PBXGroup; children = ( + F269303A2BB389BF00BF17C4 /* assets */, F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */, 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, @@ -232,6 +235,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { @@ -280,6 +284,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F269303B2BB389BF00BF17C4 /* assets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,6 +601,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsTests.m index 8da526629087..b3ac89e2441d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsTests.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsTests.m @@ -17,6 +17,10 @@ @interface FLTGoogleMapFactory (Test) @interface GoogleMapsTests : XCTestCase @end +@interface FLTTileProviderController (Testing) +- (UIImage *)handleResultTile:(nullable UIImage *)tileImage; +@end + @implementation GoogleMapsTests - (void)testPlugin { @@ -62,4 +66,23 @@ - (void)testMapsServiceSync { XCTAssertEqual(factory1.sharedMapServices, factory2.sharedMapServices); } +- (void)testHandleResultTileDownsamplesWideGamutImages { + FLTTileProviderController *controller = [[FLTTileProviderController alloc] init]; + + NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"widegamut" + ofType:@"png" + inDirectory:@"assets"]; + UIImage *wideGamutImage = [UIImage imageWithContentsOfFile:imagePath]; + + XCTAssertNotNil(wideGamutImage, @"The image should be loaded."); + + UIImage *downsampledImage = [controller handleResultTile:wideGamutImage]; + + CGImageRef imageRef = downsampledImage.CGImage; + size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); + + // non wide gamut images use 8 bit format + XCTAssert(bitsPerComponent == 8); +} + @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/assets/widegamut.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/assets/widegamut.png new file mode 100644 index 000000000000..32f032bae3de Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/assets/widegamut.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml index e5e0cd2a4b1e..a0b172a65e1c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # 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: ../../ - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 maps_example_dart: path: ../shared/maps_example_dart/ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/example_google_map.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/example_google_map.dart index f176b987e28b..0734731af7c3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/example_google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/example_google_map.dart @@ -144,12 +144,6 @@ class ExampleGoogleMapController { .moveCamera(cameraUpdate, mapId: mapId); } - /// Sets the styling of the base map. - Future setMapStyle(String? mapStyle) { - return GoogleMapsFlutterPlatform.instance - .setMapStyle(mapStyle, mapId: mapId); - } - /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); @@ -195,6 +189,11 @@ class ExampleGoogleMapController { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } + /// Returns the last style error, if any. + Future getStyleError() { + return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + } + /// Disposes of the platform resources void dispose() { GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId); @@ -245,6 +244,7 @@ class ExampleGoogleMap extends StatefulWidget { this.onTap, this.onLongPress, this.cloudMapId, + this.style, }); /// Callback method for when the map is ready to be used. @@ -353,6 +353,9 @@ class ExampleGoogleMap extends StatefulWidget { /// for more details. final String? cloudMapId; + /// The locally configured style for the map. + final String? style; + /// Creates a [State] for this [ExampleGoogleMap]. @override State createState() => _ExampleGoogleMapState(); @@ -539,5 +542,6 @@ MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) { trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, cloudMapId: map.cloudMapId, + style: map.style, ); } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/map_ui.dart index 311e2267aa0c..105676da9ed5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/map_ui.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/map_ui.dart @@ -60,6 +60,7 @@ class MapUiBodyState extends State { bool _myLocationButtonEnabled = true; late ExampleGoogleMapController _controller; bool _nightMode = false; + String _mapStyle = ''; @override void initState() { @@ -244,27 +245,16 @@ class MapUiBodyState extends State { return rootBundle.loadString(path); } - void _setMapStyle(String mapStyle) { - setState(() { - _nightMode = true; - _controller.setMapStyle(mapStyle); - }); - } - - // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { - assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), - onPressed: () { - if (_nightMode) { - setState(() { - _nightMode = false; - _controller.setMapStyle(null); - }); - } else { - _getFileData('assets/night_mode.json').then(_setMapStyle); - } + onPressed: () async { + _nightMode = !_nightMode; + final String style = + _nightMode ? await _getFileData('assets/night_mode.json') : ''; + setState(() { + _mapStyle = style; + }); }, ); } @@ -279,6 +269,7 @@ class MapUiBodyState extends State { cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, + style: _mapStyle, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, @@ -353,5 +344,11 @@ class MapUiBodyState extends State { _controller = controller; _isMapCreated = true; }); + // Log any style errors to the console for debugging. + _controller.getStyleError().then((String? error) { + if (error != null) { + debugPrint(error); + } + }); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml index 38259978c902..77063ec00061 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # 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: ../../../ - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m index 5863697d7b9b..73eab6c1eadb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m @@ -118,41 +118,64 @@ - (instancetype)init:(FlutterMethodChannel *)methodChannel #pragma mark - GMSTileLayer method +- (UIImage *)handleResultTile:(nullable UIImage *)tile { + CGImageRef imageRef = tile.CGImage; + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + BOOL isFloat = bitmapInfo && kCGBitmapFloatComponents; + size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); + + // Engine use f16 pixel format for wide gamut images + // If it is wide gamut, we want to downsample it + if (isFloat & (bitsPerComponent == 16)) { + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(nil, tile.size.width, tile.size.height, 8, 0, + colorSpace, kCGImageAlphaPremultipliedLast); + CGContextDrawImage(context, CGRectMake(0, 0, tile.size.width, tile.size.height), tile.CGImage); + CGImageRef image = CGBitmapContextCreateImage(context); + tile = [UIImage imageWithCGImage:image]; + + CGImageRelease(image); + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + } + return tile; +} + - (void)requestTileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom receiver:(id)receiver { - [self.methodChannel - invokeMethod:@"tileOverlay#getTile" - arguments:@{ - @"tileOverlayId" : self.tileOverlayIdentifier, - @"x" : @(x), - @"y" : @(y), - @"zoom" : @(zoom) - } - result:^(id _Nullable result) { - UIImage *tileImage; - if ([result isKindOfClass:[NSDictionary class]]) { - FlutterStandardTypedData *typedData = (FlutterStandardTypedData *)result[@"data"]; - if (typedData == nil) { - tileImage = kGMSTileLayerNoTile; - } else { - tileImage = [UIImage imageWithData:typedData.data]; - } - } else { - if ([result isKindOfClass:[FlutterError class]]) { - FlutterError *error = (FlutterError *)result; - NSLog(@"Can't get tile: errorCode = %@, errorMessage = %@, details = %@", - [error code], [error message], [error details]); - } - if ([result isKindOfClass:[FlutterMethodNotImplemented class]]) { - NSLog(@"Can't get tile: notImplemented"); - } - tileImage = kGMSTileLayerNoTile; - } - - [receiver receiveTileWithX:x y:y zoom:zoom image:tileImage]; - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.methodChannel invokeMethod:@"tileOverlay#getTile" + arguments:@{ + @"tileOverlayId" : self.tileOverlayIdentifier, + @"x" : @(x), + @"y" : @(y), + @"zoom" : @(zoom) + } + result:^(id _Nullable result) { + UIImage *tileImage; + if ([result isKindOfClass:[NSDictionary class]]) { + FlutterStandardTypedData *typedData = (FlutterStandardTypedData *)result[@"data"]; + if (typedData == nil) { + tileImage = kGMSTileLayerNoTile; + } else { + tileImage = [self handleResultTile:[UIImage imageWithData:typedData.data]]; + } + } else { + if ([result isKindOfClass:[FlutterError class]]) { + FlutterError *error = (FlutterError *)result; + NSLog(@"Can't get tile: errorCode = %@, errorMessage = %@, details = %@", + [error code], [error message], [error details]); + } + if ([result isKindOfClass:[FlutterMethodNotImplemented class]]) { + NSLog(@"Can't get tile: notImplemented"); + } + tileImage = kGMSTileLayerNoTile; + } + [receiver receiveTileWithX:x y:y zoom:zoom image:tileImage]; + }]; + }); } @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m index 06e7838d9f4d..0b9b09b7748c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m @@ -67,6 +67,11 @@ @interface FLTGoogleMapController () @property(nonatomic, strong) FLTPolylinesController *polylinesController; @property(nonatomic, strong) FLTCirclesController *circlesController; @property(nonatomic, strong) FLTTileOverlaysController *tileOverlaysController; +// The resulting error message, if any, from the last attempt to set the map style. +// This is used to provide access to errors after the fact, since the map style is generally set at +// creation time and there's no mechanism to return non-fatal error details during platform view +// initialization. +@property(nonatomic, copy) NSString *styleError; @end @@ -400,13 +405,15 @@ - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSNumber *isBuildingsEnabled = @(self.mapView.buildingsEnabled); result(isBuildingsEnabled); } else if ([call.method isEqualToString:@"map#setStyle"]) { - NSString *mapStyle = [call arguments]; - NSString *error = [self setMapStyle:mapStyle]; - if (error == nil) { + id mapStyle = [call arguments]; + self.styleError = [self setMapStyle:(mapStyle == [NSNull null] ? nil : mapStyle)]; + if (self.styleError == nil) { result(@[ @(YES) ]); } else { - result(@[ @(NO), error ]); + result(@[ @(NO), self.styleError ]); } + } else if ([call.method isEqualToString:@"map#getStyleError"]) { + result(self.styleError); } else if ([call.method isEqualToString:@"map#getTileOverlayInfo"]) { NSString *rawTileOverlayId = call.arguments[@"tileOverlayId"]; result([self.tileOverlaysController tileOverlayInfoWithIdentifier:rawTileOverlayId]); @@ -506,7 +513,7 @@ - (void)setMyLocationButtonEnabled:(BOOL)enabled { } - (NSString *)setMapStyle:(NSString *)mapStyle { - if (mapStyle == (id)[NSNull null] || mapStyle.length == 0) { + if (mapStyle.length == 0) { self.mapView.mapStyle = nil; return nil; } @@ -658,6 +665,10 @@ - (void)interpretMapOptions:(NSDictionary *)data { if (myLocationButtonEnabled && myLocationButtonEnabled != (id)[NSNull null]) { [self setMyLocationButtonEnabled:[myLocationButtonEnabled boolValue]]; } + NSString *style = data[@"style"]; + if (style) { + self.styleError = [self setMapStyle:style]; + } } @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h index 84f6f7ca485f..1377b68e9b6e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h @@ -9,14 +9,12 @@ NS_ASSUME_NONNULL_BEGIN @interface FLTGoogleMapController (Test) -/** - * Initializes a map controller with a concrete map view. - * - * @param mapView A map view that will be displayed by the controller - * @param viewId A unique identifier for the controller. - * @param args Parameters for initialising the map view. - * @param registrar The plugin registrar passed from Flutter. - */ +/// Initializes a map controller with a concrete map view. +/// +/// @param mapView A map view that will be displayed by the controller +/// @param viewId A unique identifier for the controller. +/// @param args Parameters for initialising the map view. +/// @param registrar The plugin registrar passed from Flutter. - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index 642febcdd3b9..56f3cc5a0794 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -466,6 +466,12 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return _channel(mapId).invokeMethod('map#takeSnapshot'); } + @override + Future getStyleError({required int mapId}) { + final MethodChannel channel = ensureChannelInitialized(mapId); + return channel.invokeMethod('map#getStyleError'); + } + Widget _buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { @@ -618,6 +624,7 @@ Map _jsonForMapConfiguration(MapConfiguration config) { if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!, + if (config.style != null) 'style': config.style!, }; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index 502db892ac53..0a3d3e0894fa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios description: iOS implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.4.2 +version: 2.5.2 environment: sdk: ^3.2.3 @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 stream_transform: ^2.0.0 dev_dependencies: diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS index 493a0b4ef9c2..6f33f4aa0511 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +Joonas Kerttula diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 1905f2c44e9f..668dcb2731b3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,5 +1,13 @@ -## NEXT +## 2.6.0 +* Adds support for marker clustering. + +## 2.5.0 + +* Adds `style` to the `MapConfiguration` to allow setting style as part of + map creation. +* Adds `getStyleError` to the platform interface, to allow asynchronous access + to style errors that occur during initialization. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 2.4.3 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart index 0034633b8066..67a026d90557 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart @@ -167,3 +167,12 @@ class MapLongPressEvent extends _PositionedMapEvent { /// The `position` of this event is the LatLng where the Map was long pressed. MapLongPressEvent(int mapId, LatLng position) : super(mapId, position, null); } + +/// An event fired when a cluster icon managed by [ClusterManager] is tapped. +class ClusterTapEvent extends MapEvent { + /// Build a ClusterTapEvent Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [Cluster] object that represents the tapped + /// cluster icon managed by [ClusterManager]. + ClusterTapEvent(super.mapId, super.cluster); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart index 2c79c0cf995c..dd728cf1349d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -166,6 +166,11 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onClusterTap({required int mapId}) { + return _events(mapId).whereType(); + } + Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': @@ -258,6 +263,36 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { arguments['zoom'] as int?, ); return tile.toJson(); + case 'cluster#onTap': + final Map arguments = _getArgumentDictionary(call); + final ClusterManagerId clusterManagerId = + ClusterManagerId(arguments['clusterManagerId']! as String); + final LatLng position = LatLng.fromJson(arguments['position'])!; + + final Map> latLngData = + (arguments['bounds']! as Map).map( + (dynamic key, dynamic object) => + MapEntry>( + key as String, object as List)); + + final LatLngBounds bounds = LatLngBounds( + northeast: LatLng.fromJson(latLngData['northeast'])!, + southwest: LatLng.fromJson(latLngData['southwest'])!); + + final List markerIds = + (arguments['markerIds']! as List) + .map((dynamic markerId) => MarkerId(markerId as String)) + .toList(); + + _mapEventStreamController.add(ClusterTapEvent( + mapId, + Cluster( + clusterManagerId, + markerIds, + position: position, + bounds: bounds, + ), + )); default: throw MissingPluginException(); } @@ -347,6 +382,17 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { ); } + @override + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'clusterManagers#update', + clusterManagerUpdates.toJson(), + ); + } + @override Future clearTileCache( TileOverlayId tileOverlayId, { @@ -585,6 +631,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -599,6 +646,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { polygons: polygons, polylines: polylines, circles: circles, + clusterManagers: clusterManagers, tileOverlays: tileOverlays), mapOptions: mapOptions, ); @@ -614,6 +662,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -627,6 +676,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { polylines: polylines, circles: circles, tileOverlays: tileOverlays, + clusterManagers: clusterManagers, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart index d9d0bb37e117..648c6a162d68 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart @@ -142,6 +142,20 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('updateTileOverlays() has not been implemented.'); } + /// Updates cluster manager configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) { + throw UnimplementedError( + 'updateClusterManagers() has not been implemented.'); + } + /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. /// @@ -357,11 +371,22 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('onLongPress() has not been implemented.'); } + /// A marker icon managed by [ClusterManager] has been tapped. + Stream onClusterTap({required int mapId}) { + throw UnimplementedError('onClusterTap() has not been implemented.'); + } + /// Dispose of whatever resources the `mapId` is holding on to. void dispose({required int mapId}) { throw UnimplementedError('dispose() has not been implemented.'); } + /// If the last attempt to set the style via [MapConfiguration.style] failed, + /// returns the error information, otherwise returns null. + Future getStyleError({required int mapId}) async { + return null; + } + /// Returns a widget displaying the map view - deprecated, use /// [buildViewWithConfiguration] instead. Widget buildView( diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart index 1e07b97c300d..461d38a61616 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart @@ -115,4 +115,10 @@ abstract class GoogleMapsInspectorPlatform extends PlatformInterface { {required int mapId}) { throw UnimplementedError('getTileOverlayInfo() has not been implemented.'); } + + /// Returns current clusters from [ClusterManager]. + Future> getClusters( + {required int mapId, required ClusterManagerId clusterManagerId}) { + throw UnimplementedError('getClusters() has not been implemented.'); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart new file mode 100644 index 000000000000..d5ca46a72f55 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' + show immutable, listEquals, objectRuntimeType; +import 'types.dart'; + +/// A cluster containing multiple markers. +@immutable +class Cluster { + /// Creates a cluster with its location [LatLng], bounds [LatLngBounds], + /// and list of [MarkerId]s in the cluster. + const Cluster( + this.clusterManagerId, + this.markerIds, { + required this.position, + required this.bounds, + }) : assert(markerIds.length > 0); + + /// ID of the [ClusterManager] of the cluster. + final ClusterManagerId clusterManagerId; + + /// Cluster marker location. + final LatLng position; + + /// The bounds containing all cluster markers. + final LatLngBounds bounds; + + /// List of [MarkerId]s in the cluster. + final List markerIds; + + /// Returns the number of markers in the cluster. + int get count => markerIds.length; + + @override + String toString() => + '${objectRuntimeType(this, 'Cluster')}($clusterManagerId, $position, $bounds, $markerIds)'; + + @override + bool operator ==(Object other) { + return other is Cluster && + other.clusterManagerId == clusterManagerId && + other.position == position && + other.bounds == bounds && + listEquals(other.markerIds, markerIds); + } + + @override + int get hashCode => + Object.hash(clusterManagerId, position, bounds, markerIds); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart new file mode 100644 index 000000000000..ec3c1f7e435b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart @@ -0,0 +1,82 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show immutable; +import 'types.dart'; + +/// Uniquely identifies a [ClusterManager] among [GoogleMap] clusters. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class ClusterManagerId extends MapsObjectId { + /// Creates an immutable identifier for a [ClusterManager]. + const ClusterManagerId(super.value); +} + +/// [ClusterManager] manages marker clustering for set of [Marker]s that have +/// the same [ClusterManagerId] set. +@immutable +class ClusterManager implements MapsObject { + /// Creates an immutable object for managing clustering for set of markers. + const ClusterManager({ + required this.clusterManagerId, + this.onClusterTap, + }); + + /// Uniquely identifies a [ClusterManager]. + final ClusterManagerId clusterManagerId; + + @override + ClusterManagerId get mapsId => clusterManagerId; + + /// Callback to receive tap events for cluster markers placed on this map. + final ArgumentCallback? onClusterTap; + + /// Creates a new [ClusterManager] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + ClusterManager copyWith({ + ArgumentCallback? onClusterTapParam, + }) { + return ClusterManager( + clusterManagerId: clusterManagerId, + onClusterTap: onClusterTapParam ?? onClusterTap, + ); + } + + /// Creates a new [ClusterManager] object whose values are the same as this instance. + @override + ClusterManager clone() => copyWith(); + + /// Converts this object to something serializable in JSON. + @override + Object toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, Object? value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('clusterManagerId', clusterManagerId.value); + return json; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is ClusterManager && + clusterManagerId == other.clusterManagerId; + } + + @override + int get hashCode => clusterManagerId.hashCode; + + @override + String toString() { + return 'Cluster{clusterManagerId: $clusterManagerId, onClusterTap: $onClusterTap}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart new file mode 100644 index 000000000000..167201906dd8 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'types.dart'; + +/// [ClusterManager] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class ClusterManagerUpdates extends MapsObjectUpdates { + /// Computes [ClusterManagerUpdates] given previous and current [ClusterManager]s. + ClusterManagerUpdates.from(super.previous, super.current) + : super.from(objectName: 'clusterManager'); + + /// Set of Clusters to be added in this update. + Set get clusterManagersToAdd => objectsToAdd; + + /// Set of ClusterManagerIds to be removed in this update. + Set get clusterManagerIdsToRemove => + objectIdsToRemove.cast(); + + /// Set of Clusters to be changed in this update. + Set get clusterManagersToChange => objectsToChange; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart index 3ec973fd7d0a..6abd6c641e86 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart @@ -36,6 +36,7 @@ class MapConfiguration { this.trafficEnabled, this.buildingsEnabled, this.cloudMapId, + this.style, }); /// This setting controls how the API handles gestures on the map. Web only. @@ -113,6 +114,11 @@ class MapConfiguration { /// for more details. final String? cloudMapId; + /// Locally configured JSON style. + /// + /// To clear a previously set style, set this to an empty string. + final String? style; + /// Returns a new options object containing only the values of this instance /// that are different from [other]. MapConfiguration diffFrom(MapConfiguration other) { @@ -174,6 +180,7 @@ class MapConfiguration { buildingsEnabled: buildingsEnabled != other.buildingsEnabled ? buildingsEnabled : null, cloudMapId: cloudMapId != other.cloudMapId ? cloudMapId : null, + style: style != other.style ? style : null, ); } @@ -206,6 +213,7 @@ class MapConfiguration { trafficEnabled: diff.trafficEnabled ?? trafficEnabled, buildingsEnabled: diff.buildingsEnabled ?? buildingsEnabled, cloudMapId: diff.cloudMapId ?? cloudMapId, + style: diff.style ?? style, ); } @@ -231,7 +239,8 @@ class MapConfiguration { indoorViewEnabled == null && trafficEnabled == null && buildingsEnabled == null && - cloudMapId == null; + cloudMapId == null && + style == null; @override bool operator ==(Object other) { @@ -262,7 +271,8 @@ class MapConfiguration { indoorViewEnabled == other.indoorViewEnabled && trafficEnabled == other.trafficEnabled && buildingsEnabled == other.buildingsEnabled && - cloudMapId == other.cloudMapId; + cloudMapId == other.cloudMapId && + style == other.style; } @override @@ -288,5 +298,6 @@ class MapConfiguration { trafficEnabled, buildingsEnabled, cloudMapId, + style, ]); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart index 56f80e8312dd..009a6a078268 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart @@ -21,6 +21,7 @@ class MapObjects { this.polylines = const {}, this.circles = const {}, this.tileOverlays = const {}, + this.clusterManagers = const {}, }); final Set markers; @@ -28,4 +29,5 @@ class MapObjects { final Set polylines; final Set circles; final Set tileOverlays; + final Set clusterManagers; } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart index 0b5b1a507d37..8c4d7b267979 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart @@ -151,6 +151,7 @@ class Marker implements MapsObject { this.rotation = 0.0, this.visible = true, this.zIndex = 0.0, + this.clusterManagerId, this.onTap, this.onDrag, this.onDragStart, @@ -163,6 +164,9 @@ class Marker implements MapsObject { @override MarkerId get mapsId => markerId; + /// Marker clustering is managed by [ClusterManager] with [clusterManagerId]. + final ClusterManagerId? clusterManagerId; + /// The opacity of the marker, between 0.0 and 1.0 inclusive. /// /// 0.0 means fully transparent, 1.0 means fully opaque. @@ -241,6 +245,7 @@ class Marker implements MapsObject { ValueChanged? onDragStartParam, ValueChanged? onDragParam, ValueChanged? onDragEndParam, + ClusterManagerId? clusterManagerIdParam, }) { return Marker( markerId: markerId, @@ -259,6 +264,7 @@ class Marker implements MapsObject { onDragStart: onDragStartParam ?? onDragStart, onDrag: onDragParam ?? onDrag, onDragEnd: onDragEndParam ?? onDragEnd, + clusterManagerId: clusterManagerIdParam ?? clusterManagerId, ); } @@ -289,6 +295,7 @@ class Marker implements MapsObject { addIfPresent('rotation', rotation); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); + addIfPresent('clusterManagerId', clusterManagerId?.value); return json; } @@ -312,7 +319,8 @@ class Marker implements MapsObject { position == other.position && rotation == other.rotation && visible == other.visible && - zIndex == other.zIndex; + zIndex == other.zIndex && + clusterManagerId == other.clusterManagerId; } @override @@ -324,6 +332,6 @@ class Marker implements MapsObject { 'consumeTapEvents: $consumeTapEvents, draggable: $draggable, flat: $flat, ' 'icon: $icon, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' 'visible: $visible, zIndex: $zIndex, onTap: $onTap, onDragStart: $onDragStart, ' - 'onDrag: $onDrag, onDragEnd: $onDragEnd}'; + 'onDrag: $onDrag, onDragEnd: $onDragEnd, clusterManagerId: $clusterManagerId}'; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart index 1f1916b1c55e..3ef0e4ab18b5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart @@ -9,6 +9,9 @@ export 'camera.dart'; export 'cap.dart'; export 'circle.dart'; export 'circle_updates.dart'; +export 'cluster.dart'; +export 'cluster_manager.dart'; +export 'cluster_manager_updates.dart'; export 'joint_type.dart'; export 'location.dart'; export 'map_configuration.dart'; @@ -30,6 +33,7 @@ export 'tile_provider.dart'; export 'ui.dart'; // Export the utils used by the Widget export 'utils/circle.dart'; +export 'utils/cluster_manager.dart'; export 'utils/marker.dart'; export 'utils/polygon.dart'; export 'utils/polyline.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart new file mode 100644 index 000000000000..c44578c04d04 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../types.dart'; +import 'maps_object.dart'; + +/// Converts an [Iterable] of Cluster Managers in a Map of ClusterManagerId -> Cluster. +Map keyByClusterManagerId( + Iterable clusterManagers) { + return keyByMapsObjectId(clusterManagers) + .cast(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart index 3c3e0b714cb6..43b25fa642da 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart @@ -59,5 +59,6 @@ Map jsonForMapConfiguration(MapConfiguration config) { if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!, + if (config.style != null) 'style': config.style!, }; } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index 2f30c38978f5..ec9271aab715 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%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: 2.4.3 +version: 2.6.0 environment: sdk: ^3.1.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart index d1dba2b75b55..b0e48fd474bb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart @@ -83,6 +83,44 @@ void main() { ); }, ); + + test( + 'updateClusterManagers() throws UnimplementedError', + () { + expect( + () => BuildViewGoogleMapsFlutterPlatform().updateClusterManagers( + ClusterManagerUpdates.from( + { + const ClusterManager( + clusterManagerId: ClusterManagerId('123')) + }, + { + const ClusterManager( + clusterManagerId: ClusterManagerId('456')) + }, + ), + mapId: 0), + throwsUnimplementedError); + }, + ); + + test( + 'onClusterTap() throws UnimplementedError', + () { + expect( + () => BuildViewGoogleMapsFlutterPlatform().onClusterTap(mapId: 0), + throwsUnimplementedError); + }, + ); + + test( + 'default implementation of `getStyleError` returns null', + () async { + final GoogleMapsFlutterPlatform platform = + BuildViewGoogleMapsFlutterPlatform(); + expect(await platform.getStyleError(mapId: 0), null); + }, + ); }); } @@ -106,6 +144,7 @@ class BuildViewGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart new file mode 100644 index 000000000000..073c6c3dbec9 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$ClusterManager', () { + test('constructor defaults', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + + expect(manager.clusterManagerId, const ClusterManagerId('1234')); + }); + + test('toJson', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + + final Map json = manager.toJson() as Map; + + expect(json, { + 'clusterManagerId': '1234', + }); + }); + test('clone', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + final ClusterManager clone = manager.clone(); + + expect(identical(clone, manager), isFalse); + expect(clone, equals(manager)); + }); + test('copyWith', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + final List log = []; + + final ClusterManager copy = manager.copyWith( + onClusterTapParam: (Cluster cluster) { + log.add('onTapParam'); + }, + ); + copy.onClusterTap!(Cluster( + manager.clusterManagerId, const [MarkerId('5678')], + position: const LatLng(11.0, 22.0), + bounds: LatLngBounds( + southwest: const LatLng(22.0, 33.0), + northeast: const LatLng(33.0, 88.0)))); + expect(log, contains('onTapParam')); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart new file mode 100644 index 000000000000..26a69ed0ee3b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$Cluster', () { + test('constructor', () { + final Cluster cluster = Cluster( + const ClusterManagerId('3456787654'), + const [MarkerId('23456')], + position: const LatLng(55.0, 66.0), + bounds: LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + ), + ); + + expect(cluster.clusterManagerId.value, equals('3456787654')); + expect(cluster.markerIds[0].value, equals('23456')); + expect(cluster.position, equals(const LatLng(55.0, 66.0))); + expect( + cluster.bounds, + LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + )); + }); + + test('constructor markerIds length is > 0', () { + void initWithMarkerIds(List markerIds) { + Cluster( + const ClusterManagerId('3456787654'), + markerIds, + position: const LatLng(55.0, 66.0), + bounds: LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + ), + ); + } + + expect(() => initWithMarkerIds([const MarkerId('12342323')]), + isNot(throwsAssertionError)); + expect(() => initWithMarkerIds([]), throwsAssertionError); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart index 2a53b8c0bbc6..e34f32676e0a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart @@ -33,6 +33,7 @@ void main() { indoorViewEnabled: false, trafficEnabled: false, buildingsEnabled: false, + style: 'diff base style', ); test('only include changed fields', () async { @@ -408,6 +409,23 @@ void main() { // The hash code should change. expect(empty.hashCode, isNot(diff.hashCode)); }); + + test('handle style', () async { + const String aStlye = 'a style'; + const MapConfiguration diff = MapConfiguration(style: aStlye); + + const MapConfiguration empty = MapConfiguration(); + final MapConfiguration updated = diffBase.applyDiff(diff); + + // A diff applied to empty options should be the diff itself. + expect(empty.applyDiff(diff), diff); + // The diff from empty options should be the diff itself. + expect(diff.diffFrom(empty), diff); + // A diff applied to non-empty options should update that field. + expect(updated.style, aStlye); + // The hash code should change. + expect(empty.hashCode, isNot(diff.hashCode)); + }); }); group('isEmpty', () { @@ -541,5 +559,11 @@ void main() { expect(diff.isEmpty, false); }); + + test('is false with style', () async { + const MapConfiguration diff = MapConfiguration(style: 'a style'); + + expect(diff.isEmpty, false); + }); }); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart index db7afcbb0398..76e5de2b1866 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart @@ -96,7 +96,8 @@ void main() { expect(clone, equals(marker)); }); test('copyWith', () { - const Marker marker = Marker(markerId: MarkerId('ABC123')); + const MarkerId markerId = MarkerId('ABC123'); + const Marker marker = Marker(markerId: markerId); final BitmapDescriptor testDescriptor = BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); @@ -111,6 +112,8 @@ void main() { const double testRotationParam = 100; final bool testVisibleParam = !marker.visible; const double testZIndexParam = 100; + const ClusterManagerId testClusterManagerIdParam = + ClusterManagerId('DEF123'); final List log = []; final Marker copy = marker.copyWith( @@ -125,6 +128,7 @@ void main() { rotationParam: testRotationParam, visibleParam: testVisibleParam, zIndexParam: testZIndexParam, + clusterManagerIdParam: testClusterManagerIdParam, onTapParam: () { log.add('onTapParam'); }, @@ -139,6 +143,7 @@ void main() { }, ); + expect(copy.markerId, equals(markerId)); expect(copy.alpha, equals(testAlphaParam)); expect(copy.anchor, equals(testAnchorParam)); expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam)); @@ -150,6 +155,7 @@ void main() { expect(copy.rotation, equals(testRotationParam)); expect(copy.visible, equals(testVisibleParam)); expect(copy.zIndex, equals(testZIndexParam)); + expect(copy.clusterManagerId, equals(testClusterManagerIdParam)); copy.onTap!(); expect(log, contains('onTapParam')); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart new file mode 100644 index 000000000000..64eab27b2d4a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/src/types/types.dart'; + +void main() { + group('keyByClusterManagerId', () { + test('returns a Map keyed by clusterManagerId', () { + const ClusterManagerId id1 = ClusterManagerId('id1'); + const ClusterManagerId id2 = ClusterManagerId('id2'); + const ClusterManagerId id3 = ClusterManagerId('id3'); + + final List clusterManagers = [ + const ClusterManager(clusterManagerId: id1), + const ClusterManager(clusterManagerId: id2), + const ClusterManager(clusterManagerId: id3), + ]; + + final Map result = + keyByClusterManagerId(clusterManagers); + + expect(result, isA>()); + expect(result[id1], equals(clusterManagers[0])); + expect(result[id2], equals(clusterManagers[1])); + expect(result[id3], equals(clusterManagers[2])); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index a8de9d0147e7..08d4608228c9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,6 +1,20 @@ -## NEXT +## 0.5.6+2 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Uses `TrustedTypes` from `web: ^0.5.1`. + +## 0.5.6+1 + +* Fixes an issue where `dart:js_interop` object literal factories did not + compile with dart2js. + +## 0.5.6 + +* Adds support for `MapConfiguration.style`. +* Adds support for `getStyleError`. + +## 0.5.5 +* Migrates to `dart:js_interop` and `package:web` APIs. +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. ## 0.5.4+3 diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart index 51c36a55a478..aa7e0ae4fe41 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart @@ -3,13 +3,14 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -230,7 +231,7 @@ void main() { polygons = MockPolygonsController(); polylines = MockPolylinesController(); tileOverlays = MockTileOverlaysController(); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); }); testWidgets('listens to map events', (WidgetTester tester) async { @@ -356,11 +357,8 @@ void main() { }); group('Initialization options', () { - gmaps.MapOptions? capturedOptions; - setUp(() { - capturedOptions = null; - }); testWidgets('translates initial options', (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; controller = createController( mapConfiguration: const MapConfiguration( mapType: MapType.satellite, @@ -389,6 +387,7 @@ void main() { testWidgets('translates fortyFiveDegreeImageryEnabled option', (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; controller = createController( mapConfiguration: const MapConfiguration( scrollGesturesEnabled: false, @@ -409,6 +408,7 @@ void main() { testWidgets('translates webGestureHandling option', (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; controller = createController( mapConfiguration: const MapConfiguration( zoomGesturesEnabled: false, @@ -428,6 +428,7 @@ void main() { testWidgets('sets initial position when passed', (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; controller = createController( initialCameraPosition: const CameraPosition( target: LatLng(43.308, -5.6910), @@ -444,6 +445,81 @@ void main() { expect(capturedOptions!.zoom, 12); expect(capturedOptions!.center, isNotNull); }); + + testWidgets('translates style option', (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; + const String style = ''' +[{ + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [{"color": "#6b9a76"}] +}]'''; + controller = createController( + mapConfiguration: const MapConfiguration(style: style)); + controller.debugSetOverrides( + createMap: (_, gmaps.MapOptions options) { + capturedOptions = options; + return map; + }); + + controller.init(); + + expect(capturedOptions, isNotNull); + expect(capturedOptions!.styles?.length, 1); + }); + + testWidgets('stores style errors for later querying', + (WidgetTester tester) async { + gmaps.MapOptions? capturedOptions; + controller = createController( + mapConfiguration: const MapConfiguration( + style: '[[invalid style', zoomControlsEnabled: true)); + controller.debugSetOverrides( + createMap: (_, gmaps.MapOptions options) { + capturedOptions = options; + return map; + }); + + controller.init(); + + expect(controller.lastStyleError, isNotNull); + // Style failures should not prevent other options from being set. + expect(capturedOptions, isNotNull); + expect(capturedOptions!.zoomControl, true); + }); + + testWidgets('setting invalid style leaves previous style', + (WidgetTester tester) async { + gmaps.MapOptions? initialCapturedOptions; + gmaps.MapOptions? updatedCapturedOptions; + const String style = ''' +[{ + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [{"color": "#6b9a76"}] +}]'''; + controller = createController( + mapConfiguration: const MapConfiguration(style: style)); + controller.debugSetOverrides( + createMap: (_, gmaps.MapOptions options) { + initialCapturedOptions = options; + return map; + }, + setOptions: (gmaps.MapOptions options) { + updatedCapturedOptions = options; + }, + ); + + controller.init(); + controller.updateMapConfiguration( + const MapConfiguration(style: '[[invalid style')); + + expect(initialCapturedOptions, isNotNull); + expect(initialCapturedOptions!.styles?.length, 1); + // The styles should not have changed. + expect( + updatedCapturedOptions!.styles, initialCapturedOptions!.styles); + }); }); group('Traffic Layer', () { @@ -471,7 +547,7 @@ void main() { setUp(() { map = gmaps.GMap( - html.DivElement(), + createDivElement(), gmaps.MapOptions() ..zoom = 10 ..center = gmaps.LatLng(0, 0), @@ -481,7 +557,7 @@ void main() { ..init(); }); - group('updateRawOptions', () { + group('updateMapConfiguration', () { testWidgets('can update `options`', (WidgetTester tester) async { controller.updateMapConfiguration(const MapConfiguration( mapType: MapType.satellite, @@ -505,6 +581,27 @@ void main() { expect(controller.trafficLayer, isNull); }); + + testWidgets('can update style', (WidgetTester tester) async { + const String style = ''' +[{ + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [{"color": "#6b9a76"}] +}]'''; + controller + .updateMapConfiguration(const MapConfiguration(style: style)); + + expect(controller.styles.length, 1); + }); + + testWidgets('can update style', (WidgetTester tester) async { + controller.updateMapConfiguration( + const MapConfiguration(style: '[[[invalid style')); + + expect(controller.styles, isEmpty); + expect(controller.lastStyleError, isNotNull); + }); }); group('viewport getters', () { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart index 76bf2e672a3e..3f84b40adc88 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart @@ -123,9 +123,17 @@ class MockGoogleMapController extends _i1.Mock returnValueForMissingStub: false, ) as bool); + @override + List<_i5.MapTypeStyle> get styles => (super.noSuchMethod( + Invocation.getter(#styles), + returnValue: <_i5.MapTypeStyle>[], + returnValueForMissingStub: <_i5.MapTypeStyle>[], + ) as List<_i5.MapTypeStyle>); + @override void debugSetOverrides({ _i4.DebugCreateMapFunction? createMap, + _i4.DebugSetOptionsFunction? setOptions, _i4.MarkersController? markers, _i4.CirclesController? circles, _i4.PolygonsController? polygons, @@ -138,6 +146,7 @@ class MockGoogleMapController extends _i1.Mock [], { #createMap: createMap, + #setOptions: setOptions, #markers: markers, #circles: circles, #polygons: polygons, diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart index 2e2d77b71dec..0b284cac7663 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; /// Test Markers @@ -123,7 +124,7 @@ void main() { testWidgets('showInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, @@ -138,7 +139,7 @@ void main() { testWidgets('hideInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, @@ -156,7 +157,7 @@ void main() { setUp(() { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); - final gmaps.GMap map = gmaps.GMap(html.DivElement()); + final gmaps.GMap map = gmaps.GMap(createDivElement()); marker.set('map', map); controller = MarkerController(marker: marker, infoWindow: infoWindow); }); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart index 6a264064a3ca..d965435b3a55 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:html' as html; import 'dart:typed_data'; import 'dart:ui'; @@ -12,8 +11,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart'; import 'resources/icon_image_base64.dart'; @@ -28,7 +30,7 @@ void main() { setUp(() { events = StreamController>(); controller = MarkersController(stream: events); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); controller.bindToMap(123, map); }); @@ -274,11 +276,11 @@ void main() { controller.addMarkers(markers); expect(controller.markers.length, 1); - final html.HtmlElement? content = controller.markers[const MarkerId('1')] - ?.infoWindow?.content as html.HtmlElement?; - expect(content?.innerHtml, contains('title for test')); + final HTMLElement? content = controller + .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?; + expect(content?.innerHTML, contains('title for test')); expect( - content?.innerHtml, + content?.innerHTML, contains( 'Go to Google >>>', )); @@ -299,8 +301,8 @@ void main() { controller.addMarkers(markers); expect(controller.markers.length, 1); - final html.HtmlElement? content = controller.markers[const MarkerId('1')] - ?.infoWindow?.content as html.HtmlElement?; + final HTMLElement? content = controller + .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?; content?.click(); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart index 29f902f7f1e0..0724da211275 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart @@ -3,13 +3,14 @@ // found in the LICENSE file. import 'dart:convert'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart'; import 'resources/tile16_base64.dart'; @@ -43,8 +44,10 @@ void main() { final gmaps.Size size = controller.gmMapType.tileSize!; expect(size.width, TileOverlayController.logicalTileSize); expect(size.height, TileOverlayController.logicalTileSize); - expect(controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document), - null); + expect( + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document), + null, + ); }); testWidgets('produces image tiles', (WidgetTester tester) async { @@ -55,16 +58,16 @@ void main() { ), ); - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; expect(img.naturalWidth, 0); expect(img.naturalHeight, 0); expect(img.hidden, true); - // Wait until the image is fully loaded and decoded before re-reading its attributes. await img.onLoad.first; - await img.decode(); + + await img.decode().toDart; expect(img.hidden, false); expect(img.naturalWidth, 16); @@ -79,9 +82,9 @@ void main() { ), ); { - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; await null; // let `getTile` `then` complete expect( img.src, @@ -95,10 +98,12 @@ void main() { tileProvider: TestTileProvider(), )); { - final html.ImageElement img = - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)! - as html.ImageElement; + final HTMLImageElement img = + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document)! + as HTMLImageElement; + await img.onLoad.first; + expect( img.src, isNotEmpty, @@ -109,7 +114,7 @@ void main() { controller.update(const TileOverlay(tileOverlayId: id)); { expect( - controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document), + controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, document), null, reason: 'Setting a null tileProvider should work.', ); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart index 8b6b34694f46..c680c97a993a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,9 +10,12 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' hide GoogleMapController; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:web/web.dart'; @GenerateNiceMocks(>[MockSpec()]) import 'overlays_test.mocks.dart'; @@ -38,13 +40,13 @@ void main() { /// 0. void probeTiles() { for (final gmaps.MapType? mapType in map.overlayMapTypes!.array!) { - mapType?.getTile!(gmaps.Point(0, 0), 0, html.document); + mapType?.getTile!(gmaps.Point(0, 0), 0, document); } } setUp(() { controller = TileOverlaysController(); - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); controller.googleMap = map; tileProviders = [ diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart index e64bd43561a2..c5d2ff7aba24 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart @@ -71,7 +71,10 @@ void main() { expect(await controller.getZoomLevel(), 12); expect(coords.latitude, closeTo(19, _acceptableLatLngDelta)); expect(coords.longitude, closeTo(26, _acceptableLatLngDelta)); - }); + }, + // TODO(bparrishMines): This is failing due to an error being thrown after + // completion. See https://github.com/flutter/flutter/issues/145149 + skip: true); testWidgets('addPadding', (WidgetTester tester) async { const LatLng initialMapCenter = LatLng(0, 0); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart index b9bc2d371c9b..ad3d3d4a8a4f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; @@ -11,6 +10,8 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps/google_maps_geometry.dart' as geometry; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +// ignore: implementation_imports +import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; // This value is used when comparing the results of @@ -25,7 +26,7 @@ void main() { late gmaps.GMap map; setUp(() { - map = gmaps.GMap(html.DivElement()); + map = gmaps.GMap(createDivElement()); }); group('CirclesController', () { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart index 6bd536109f7a..c32edb4b8ba8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart @@ -20,6 +20,9 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { @override Widget build(BuildContext context) { - return const Text('Testing... Look at the console output for results!'); + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('Testing... Look at the console output for results!'), + ); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 7b2c31b6f537..28381575ea0a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -3,21 +3,22 @@ publish_to: none # Tests require flutter beta or greater to run. environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 google_maps_flutter_web: path: ../ + web: ^0.5.0 dev_dependencies: build_runner: ^2.1.1 flutter_test: sdk: flutter - google_maps: ^6.1.0 + google_maps: ^7.1.0 google_maps_flutter: ^2.2.0 # Needed for projection_test.dart http: ">=0.13.0 <2.0.0" integration_test: diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index d1ef18d3067e..fe44d41aa484 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -6,8 +6,7 @@ library google_maps_flutter_web; import 'dart:async'; import 'dart:convert'; -import 'dart:html' hide VoidCallback; -import 'dart:js_util'; +import 'dart:js_interop'; import 'dart:ui_web' as ui_web; import 'package:collection/collection.dart'; @@ -20,10 +19,14 @@ import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:sanitize_html/sanitize_html.dart'; import 'package:stream_transform/stream_transform.dart'; +import 'package:web/web.dart'; +import 'src/dom_window_extension.dart'; import 'src/google_maps_inspector_web.dart'; +import 'src/map_styler.dart'; import 'src/third_party/to_screen_location/to_screen_location.dart'; import 'src/types.dart'; +import 'src/utils.dart'; part 'src/circle.dart'; part 'src/circles.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 8ca6a75559f4..0f73a7df0445 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -9,6 +9,9 @@ final gmaps.LatLng _nullGmapsLatLng = gmaps.LatLng(0, 0); final gmaps.LatLngBounds _nullGmapsLatLngBounds = gmaps.LatLngBounds(_nullGmapsLatLng, _nullGmapsLatLng); +// The TrustedType Policy used by this plugin. Used to sanitize InfoWindow contents. +TrustedTypePolicy? _gmapsTrustedTypePolicy; + // Converts a [Color] into a valid CSS value #RRGGBB. String _getCssColor(Color color) { return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}'; @@ -28,7 +31,7 @@ double _getCssOpacity(Color color) { // mapToolbarEnabled is unused in web, there's no "map toolbar" // myLocationButtonEnabled Widget not available in web yet, it needs to be built on top of the maps widget // See: https://developers.google.com/maps/documentation/javascript/examples/control-custom -// myLocationEnabled needs to be built through dart:html navigator.geolocation +// myLocationEnabled needs to be built through `navigator.geolocation` from package:web. // See: https://api.dart.dev/stable/2.8.4/dart-html/Geolocation-class.html // trafficEnabled is handled when creating the GMap object, since it needs to be added as a layer. // trackCameraPosition is just a boolean value that indicates if the map has an onCameraMove handler. @@ -80,6 +83,7 @@ gmaps.MapOptions _configurationAndStyleToGmapsOptions( options.fullscreenControl = false; options.streetViewControl = false; + // See updateMapConfiguration for why this is not using configuration.style. options.styles = styles; options.mapId = configuration.cloudMapId; @@ -139,10 +143,11 @@ List _mapStyles(String? mapStyleJson) { styles = (json.decode(mapStyleJson, reviver: (Object? key, Object? value) { if (value is Map && _isJsonMapStyle(value as Map)) { - List stylers = []; + List stylers = []; if (value['stylers'] != null) { stylers = (value['stylers']! as List) - .map((Object? e) => e != null ? jsify(e) : null) + .whereType>() + .map(MapStyler.fromJson) .toList(); } return gmaps.MapTypeStyle() @@ -203,30 +208,44 @@ gmaps.InfoWindowOptions? _infoWindowOptionsFromMarker(Marker marker) { // Add an outer wrapper to the contents of the infowindow, we need it to listen // to click events... - final HtmlElement container = DivElement() + final HTMLElement container = createDivElement() ..id = 'gmaps-marker-${marker.markerId.value}-infowindow'; if (markerTitle.isNotEmpty) { - final HtmlElement title = HeadingElement.h3() - ..className = 'infowindow-title' - ..innerText = markerTitle; - container.children.add(title); + final HTMLHeadingElement title = + (document.createElement('h3') as HTMLHeadingElement) + ..className = 'infowindow-title' + ..innerText = markerTitle; + container.appendChild(title); } if (markerSnippet.isNotEmpty) { - final HtmlElement snippet = DivElement() - ..className = 'infowindow-snippet' + final HTMLElement snippet = createDivElement() + ..className = 'infowindow-snippet'; + + // Firefox and Safari don't support Trusted Types yet. + // See https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory#browser_compatibility + if (window.nullableTrustedTypes != null) { + _gmapsTrustedTypePolicy ??= window.trustedTypes.createPolicy( + 'google_maps_flutter_sanitize', + TrustedTypePolicyOptions( + createHTML: (String html) { + return sanitizeHtml(html).toJS; + }.toJS, + ), + ); + + snippet.trustedInnerHTML = + _gmapsTrustedTypePolicy!.createHTMLNoArgs(markerSnippet); + } else { // `sanitizeHtml` is used to clean the (potential) user input from (potential) // XSS attacks through the contents of the marker InfoWindow. // See: https://pub.dev/documentation/sanitize_html/latest/sanitize_html/sanitizeHtml.html // See: b/159137885, b/159598165 - // The NodeTreeSanitizer.trusted just tells setInnerHtml to leave the output - // of `sanitizeHtml` untouched. // ignore: unsafe_html - ..setInnerHtml( - sanitizeHtml(markerSnippet), - treeSanitizer: NodeTreeSanitizer.trusted, - ); - container.children.add(snippet); + snippet.innerHTML = sanitizeHtml(markerSnippet); + } + + container.appendChild(snippet); } return gmaps.InfoWindowOptions() @@ -274,8 +293,18 @@ gmaps.Icon? _gmIconFromBitmapDescriptor(BitmapDescriptor bitmapDescriptor) { // Grab the bytes, and put them into a blob final List bytes = iconConfig[1]! as List; // Create a Blob from bytes, but let the browser figure out the encoding - final Blob blob = Blob([bytes]); - icon = gmaps.Icon()..url = Url.createObjectUrlFromBlob(blob); + final Blob blob; + + assert( + bytes is Uint8List, + 'The bytes for a BitmapDescriptor icon must be a Uint8List', + ); + + // TODO(ditman): Improve this conversion + // See https://github.com/dart-lang/web/issues/180 + blob = Blob([(bytes as Uint8List).toJS].toJS); + + icon = gmaps.Icon()..url = URL.createObjectURL(blob as JSObject); final gmaps.Size? size = _gmSizeFromIconConfig(iconConfig, 2); if (size != null) { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart new file mode 100644 index 000000000000..8c75229eb0cb --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/dom_window_extension.dart @@ -0,0 +1,41 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(srujzs): Needed for https://github.com/dart-lang/sdk/issues/54801. Once +// we publish a version with a min SDK constraint that contains this fix, +// remove. +@JS() +library; + +import 'dart:js_interop'; +import 'package:web/web.dart' as web; + +/// This extension gives [web.Window] a nullable getter to the `trustedTypes` +/// property, which is used to check for feature support. +extension NullableTrustedTypesGetter on web.Window { + /// (Nullable) Bindings to window.trustedTypes. + /// + /// This may be null if the browser doesn't support the Trusted Types API. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API + @JS('trustedTypes') + external web.TrustedTypePolicyFactory? get nullableTrustedTypes; +} + +/// This extension provides a setter for the [web.HTMLElement] `innerHTML` property, +/// that accepts trusted HTML only. +extension TrustedInnerHTML on web.HTMLElement { + /// Set the inner HTML of this element to the given [trustedHTML]. + @JS('innerHTML') + external set trustedInnerHTML(web.TrustedHTML trustedHTML); +} + +/// Allows creating a TrustedHTML object from a string, with no arguments. +extension CreateHTMLNoArgs on web.TrustedTypePolicy { + /// Allows calling `createHTML` with only the `input` argument. + @JS('createHTML') + external web.TrustedHTML createHTMLNoArgs( + String input, + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 494029ce02c5..c60dd92a0aca 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -7,7 +7,11 @@ part of '../google_maps_flutter_web.dart'; /// Type used when passing an override to the _createMap function. @visibleForTesting typedef DebugCreateMapFunction = gmaps.GMap Function( - HtmlElement div, gmaps.MapOptions options); + HTMLElement div, gmaps.MapOptions options); + +/// Type used when passing an override to the _setOptions function. +@visibleForTesting +typedef DebugSetOptionsFunction = void Function(gmaps.MapOptions options); /// Encapsulates a [gmaps.GMap], its events, and where in the DOM it's rendered. class GoogleMapController { @@ -32,11 +36,12 @@ class GoogleMapController { _polylinesController = PolylinesController(stream: _streamController); _markersController = MarkersController(stream: _streamController); _tileOverlaysController = TileOverlaysController(); + _updateStylesFromConfiguration(mapConfiguration); // Register the view factory that will hold the `_div` that holds the map in the DOM. // The `_div` needs to be created outside of the ViewFactory (and cached!) so we can // use it to create the [gmaps.GMap] in the `init()` method of this class. - _div = DivElement() + _div = createDivElement() ..id = _getViewType(mapId) ..style.width = '100%' ..style.height = '100%'; @@ -60,6 +65,8 @@ class GoogleMapController { // Caching this allows us to re-create the map faithfully when needed. MapConfiguration _lastMapConfiguration = const MapConfiguration(); List _lastStyles = const []; + // The last error resulting from providing a map style, if any. + String? _lastStyleError; /// Configuration accessor for the [GoogleMapsInspectorWeb]. /// @@ -74,7 +81,7 @@ class GoogleMapController { // The Flutter widget that contains the rendered Map. HtmlElementView? _widget; - late HtmlElement _div; + late HTMLElement _div; /// The Flutter widget that will contain the rendered Map. Used for caching. Widget? get widget { @@ -122,6 +129,7 @@ class GoogleMapController { @visibleForTesting void debugSetOverrides({ DebugCreateMapFunction? createMap, + DebugSetOptionsFunction? setOptions, MarkersController? markers, CirclesController? circles, PolygonsController? polygons, @@ -129,6 +137,7 @@ class GoogleMapController { TileOverlaysController? tileOverlays, }) { _overrideCreateMap = createMap; + _overrideSetOptions = setOptions; _markersController = markers ?? _markersController; _circlesController = circles ?? _circlesController; _polygonsController = polygons ?? _polygonsController; @@ -137,8 +146,9 @@ class GoogleMapController { } DebugCreateMapFunction? _overrideCreateMap; + DebugSetOptionsFunction? _overrideSetOptions; - gmaps.GMap _createMap(HtmlElement div, gmaps.MapOptions options) { + gmaps.GMap _createMap(HTMLElement div, gmaps.MapOptions options) { if (_overrideCreateMap != null) { return _overrideCreateMap!(div, options); } @@ -279,15 +289,36 @@ class GoogleMapController { return _lastMapConfiguration; } + // TODO(stuartmorgan): Refactor so that _lastMapConfiguration.style is the + // source of truth for style info. Currently it's tracked and handled + // separately since style didn't used to be part of the configuration. + List _updateStylesFromConfiguration( + MapConfiguration update) { + if (update.style != null) { + // Provide async access to the error rather than throwing, to match the + // behavior of other platforms where there's no mechanism to return errors + // from configuration updates. + try { + _lastStyles = _mapStyles(update.style); + _lastStyleError = null; + } on MapStyleException catch (e) { + _lastStyleError = e.cause; + } + } + return _lastStyles; + } + /// Updates the map options from a [MapConfiguration]. /// /// This method converts the map into the proper [gmaps.MapOptions]. void updateMapConfiguration(MapConfiguration update) { assert(_googleMap != null, 'Cannot update options on a null map.'); + final List styles = + _updateStylesFromConfiguration(update); final MapConfiguration newConfiguration = _mergeConfigurations(update); final gmaps.MapOptions newOptions = - _configurationAndStyleToGmapsOptions(newConfiguration, _lastStyles); + _configurationAndStyleToGmapsOptions(newConfiguration, styles); _setOptions(newOptions); _setTrafficLayer(_googleMap!, newConfiguration.trafficEnabled ?? false); @@ -300,9 +331,19 @@ class GoogleMapController { _configurationAndStyleToGmapsOptions(_lastMapConfiguration, styles)); } + /// A getter for the current styles. Only for tests. + @visibleForTesting + List get styles => _lastStyles; + + /// Returns the last error from setting the map's style, if any. + String? get lastStyleError => _lastStyleError; + // Sets new [gmaps.MapOptions] on the wrapped map. // ignore: use_setters_to_change_properties void _setOptions(gmaps.MapOptions options) { + if (_overrideSetOptions != null) { + return _overrideSetOptions!(options); + } _googleMap?.options = options; } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 140fd3e1888d..805887f0d7f5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -279,6 +279,11 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Future getStyleError({required int mapId}) async { + return _map(mapId).lastStyleError; + } + /// Disposes of the current map. It can't be used afterwards! @override void dispose({required int mapId}) { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart new file mode 100644 index 000000000000..d724cdd258e5 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_styler.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(srujzs): Needed for https://github.com/dart-lang/sdk/issues/54801. Once +// we publish a version with a min SDK constraint that contains this fix, +// remove. +@JS() +library; + +import 'dart:js_interop'; + +/// The interop type for a Google Maps Map Styler. +/// +/// See: https://developers.google.com/maps/documentation/javascript/style-reference#stylers +@JS() +extension type MapStyler._(JSObject _) implements JSObject { + /// Create a new [MapStyler] instance. + external factory MapStyler({ + String? hue, + num? lightness, + num? saturation, + num? gamma, + // ignore: non_constant_identifier_names + bool? invert_lightness, + String? visibility, + String? color, + int? weight, + }); + + /// Create a new [MapStyler] instance from the given [json]. + factory MapStyler.fromJson(Map json) { + return MapStyler( + hue: json['hue'] as String?, + lightness: json['lightness'] as num?, + saturation: json['saturation'] as num?, + gamma: json['gamma'] as num?, + invert_lightness: json['invert_lightness'] as bool?, + visibility: json['visibility'] as String?, + color: json['color'] as String?, + weight: json['weight'] as int?, + ); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart index 77258504ef67..c1b0772a1394 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart @@ -69,12 +69,12 @@ class MarkerController { /// This cannot be called after [remove]. void update( gmaps.MarkerOptions options, { - HtmlElement? newInfoWindowContent, + HTMLElement? newInfoWindowContent, }) { assert(_marker != null, 'Cannot `update` Marker after calling `remove`.'); _marker!.options = options; if (_infoWindow != null && newInfoWindowContent != null) { - _infoWindow!.content = newInfoWindowContent; + _infoWindow.content = newInfoWindowContent; } } @@ -94,7 +94,7 @@ class MarkerController { void hideInfoWindow() { assert(_marker != null, 'Cannot `hideInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { - _infoWindow!.close(); + _infoWindow.close(); _infoWindowShown = false; } } @@ -105,7 +105,7 @@ class MarkerController { void showInfoWindow() { assert(_marker != null, 'Cannot `showInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { - _infoWindow!.open(_marker!.map, _marker); + _infoWindow.open(_marker!.map, _marker); _infoWindowShown = true; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart index 52a659874f45..26cb94f9ad28 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart @@ -39,11 +39,12 @@ class MarkersController extends GeometryController { // Google Maps' JS SDK does not have a click event on the InfoWindow, so // we make one... if (infoWindowOptions.content != null && - infoWindowOptions.content is HtmlElement) { - final HtmlElement content = infoWindowOptions.content! as HtmlElement; - content.onClick.listen((_) { + infoWindowOptions.content is HTMLElement) { + final HTMLElement content = infoWindowOptions.content! as HTMLElement; + + content.onclick = (JSAny? _) { _onInfoWindowTap(marker.markerId); - }); + }.toJS; } } @@ -91,7 +92,7 @@ class MarkersController extends GeometryController { _infoWindowOptionsFromMarker(marker); markerController.update( markerOptions, - newInfoWindowContent: infoWindow?.content as HtmlElement?, + newInfoWindowContent: infoWindow?.content as HTMLElement?, ); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart index 5dd092c67ee2..29823d00b9f3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart @@ -33,7 +33,7 @@ class TileOverlayController { } /// Renders a Tile for gmaps; delegating to the configured [TileProvider]. - HtmlElement? _getTile( + HTMLElement? _getTile( gmaps.Point? tileCoord, num? zoom, Document? ownerDocument, @@ -42,10 +42,10 @@ class TileOverlayController { return null; } - final ImageElement img = - ownerDocument!.createElement('img') as ImageElement; + final HTMLImageElement img = + ownerDocument!.createElement('img') as HTMLImageElement; img.width = img.height = logicalTileSize; - img.hidden = true; + img.hidden = true.toJS; img.setAttribute('decoding', 'async'); _tileOverlay.tileProvider! @@ -55,12 +55,14 @@ class TileOverlayController { return; } // Using img lets us take advantage of native decoding. - final String src = Url.createObjectUrl(Blob([tile.data])); + final String src = URL.createObjectURL( + Blob([tile.data!.toJS].toJS) as JSObject, + ); img.src = src; - img.addEventListener('load', (_) { - img.hidden = false; - Url.revokeObjectUrl(src); - }); + img.onload = (JSAny? _) { + img.hidden = false.toJS; + URL.revokeObjectURL(src); + }.toJS; }); return img; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart new file mode 100644 index 000000000000..94bc0b6b90d5 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/utils.dart @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:web/web.dart'; + +/// Convenience method to create a new [HTMLDivElement] element. +HTMLDivElement createDivElement() { + return document.createElement('div') as HTMLDivElement; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 2293fe78c14a..f5c17935406e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.5.4+3 +version: 0.5.6+2 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,10 +22,11 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - google_maps: ^6.1.0 - google_maps_flutter_platform_interface: ^2.4.0 + google_maps: ^7.1.0 + google_maps_flutter_platform_interface: ^2.5.0 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 + web: ^0.5.1 dev_dependencies: flutter_test: diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 87622e88d6e6..ddfb3ea65262 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,7 +1,8 @@ -## NEXT +## 6.1.22 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. +* Updates play-services-auth version to 21.0.0. ## 6.1.21 diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index de3ca75ca1c7..b98cfec12c62 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -59,7 +59,7 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-auth:20.7.0' + implementation 'com.google.android.gms:play-services-auth:21.0.0' implementation 'com.google.guava:guava:32.0.1-android' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 7f4e93169184..fbc7135c1bb2 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.21 +version: 6.1.22 environment: sdk: ^3.1.0 diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index 5d5716e1715d..dd958dfb6d36 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.7.5 + +* Pins GoogleSignIn to iOS SDK "7.0.0" while preparing the update to 7.1. + ## 5.7.4 * Improves type handling in Objective-C code. diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in_ios/darwin/Classes/FLTGoogleSignInPlugin.m index d7a5cd85303c..99ee0348f518 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/Classes/FLTGoogleSignInPlugin.m +++ b/packages/google_sign_in/google_sign_in_ios/darwin/Classes/FLTGoogleSignInPlugin.m @@ -329,18 +329,16 @@ - (UIViewController *)topViewController { #pragma clang diagnostic pop } -/** - * This method recursively iterate through the view hierarchy - * to return the top most view controller. - * - * It supports the following scenarios: - * - * - The view controller is presenting another view. - * - The view controller is a UINavigationController. - * - The view controller is a UITabBarController. - * - * @return The top most view controller. - */ +/// This method recursively iterate through the view hierarchy +/// to return the top most view controller. +/// +/// It supports the following scenarios: +/// +/// - The view controller is presenting another view. +/// - The view controller is a UINavigationController. +/// - The view controller is a UITabBarController. +/// +/// @return The top most view controller. - (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { if ([viewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)viewController; diff --git a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec index 8e5524343b64..10c0b64a69b8 100644 --- a/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec +++ b/packages/google_sign_in/google_sign_in_ios/darwin/google_sign_in_ios.podspec @@ -15,7 +15,7 @@ Enables Google Sign-In in Flutter apps. s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/FLTGoogleSignInPlugin.modulemap' - s.dependency 'GoogleSignIn', '~> 7.0' + s.dependency 'GoogleSignIn', '~> 7.0.0' s.static_framework = true s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index 22c153723756..81592f091696 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_ios description: iOS implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.7.4 +version: 5.7.5 environment: sdk: ^3.2.3 diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md index 73a6b806ac13..49d02d368bae 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.12.4 + +* Updates dependencies to `web: ^0.5.0` and `google_identity_services_web: ^0.3.1`. + +## 0.12.3+3 + +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. +* Prepares update to package `web: ^0.5.0`. + ## 0.12.3+2 * Fixes new lint warnings. diff --git a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml index a7c48aa6d3bd..e260814e73bb 100644 --- a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml @@ -2,14 +2,14 @@ name: google_sign_in_web_integration_tests publish_to: none environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: cupertino_icons: ^1.0.2 flutter: sdk: flutter - google_identity_services_web: ^0.3.0 + google_identity_services_web: ^0.3.1 google_sign_in_platform_interface: ^2.4.0 google_sign_in_web: path: ../ @@ -22,7 +22,7 @@ dev_dependencies: integration_test: sdk: flutter mockito: 5.4.4 - web: ">=0.3.0 <0.5.0" + web: ^0.5.0 flutter: uses-material-design: true diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/flexible_size_html_element_view.dart b/packages/google_sign_in/google_sign_in_web/lib/src/flexible_size_html_element_view.dart index 63acfb84836e..6fa019ed909a 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/flexible_size_html_element_view.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/flexible_size_html_element_view.dart @@ -73,13 +73,10 @@ class _FlexHtmlElementView extends State { /// The function called whenever an observed resize occurs. void _onResizeEntries( - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - JSArray resizes, + JSArray resizes, web.ResizeObserver observer, ) { - final web.DOMRectReadOnly rect = - resizes.toDart.cast().last.contentRect; + final web.DOMRectReadOnly rect = resizes.toDart.last.contentRect; if (rect.width > 0 && rect.height > 0) { _doResize(Size(rect.width.toDouble(), rect.height.toDouble())); } @@ -90,14 +87,10 @@ class _FlexHtmlElementView extends State { /// When mutations are received, this function attaches a Resize Observer to /// the first child of the mutation, which will drive void _onMutationRecords( - // TODO(srujzs): Remove once typed JSArrays (JSArray) get to `stable`. - // ignore: always_specify_types - JSArray mutations, + JSArray mutations, web.MutationObserver observer, ) { - mutations.toDart - .cast() - .forEach((web.MutationRecord mutation) { + for (final web.MutationRecord mutation in mutations.toDart) { if (mutation.addedNodes.length > 0) { final web.Element? element = _locateSizeProvider(mutation.addedNodes); if (element != null) { @@ -108,7 +101,7 @@ class _FlexHtmlElementView extends State { return; } } - }); + } } /// Registers a MutationObserver on the root element of the HtmlElementView. diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml index 834e666b34ae..b7ed804cc930 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android, iOS and Web. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 0.12.3+2 +version: 0.12.4 environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,10 +22,10 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - google_identity_services_web: ^0.3.0 + google_identity_services_web: ^0.3.1 google_sign_in_platform_interface: ^2.4.0 http: ">=0.13.0 <2.0.0" - web: ">=0.3.0 <0.5.0" + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/image_picker/image_picker/example/windows/flutter/CMakeLists.txt b/packages/image_picker/image_picker/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/image_picker/image_picker/example/windows/flutter/CMakeLists.txt +++ b/packages/image_picker/image_picker/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b06dcfa43c85..09624c2ceb89 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,5 +1,10 @@ -## NEXT +## 0.8.9+5 +* Bumps androidx.exifinterface:exifinterface from 1.3.6 to 1.3.7. + +## 0.8.9+4 + +* Minimizes scope of deprecation warning suppression to only the versions where it is required. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index 9a1d1773fa2a..ef035edd41b1 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -40,7 +40,7 @@ android { dependencies { implementation 'androidx.core:core:1.10.1' implementation 'androidx.annotation:annotation:1.7.1' - implementation 'androidx.exifinterface:exifinterface:1.3.6' + implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.activity:activity:1.7.2' // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. // See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7 diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index cf45f1ba3f0d..b50a1b20cd1e 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -550,10 +550,14 @@ private File createTemporaryWritableFile(String suffix) { private void grantUriPermissions(Intent intent, Uri imageUri) { PackageManager packageManager = activity.getPackageManager(); - // TODO(stuartmorgan): Add new codepath: https://github.com/flutter/flutter/issues/121816 - @SuppressWarnings("deprecation") - List compatibleActivities = - packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + List compatibleActivities; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + compatibleActivities = + packageManager.queryIntentActivities( + intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); + } else { + compatibleActivities = queryIntentActivitiesPreApi33(packageManager, intent); + } for (ResolveInfo info : compatibleActivities) { activity.grantUriPermission( @@ -563,6 +567,12 @@ private void grantUriPermissions(Intent intent, Uri imageUri) { } } + @SuppressWarnings("deprecation") + private static List queryIntentActivitiesPreApi33( + PackageManager packageManager, Intent intent) { + return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + } + @Override public boolean onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java index 5e80258a00d5..6a93c69feb49 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java @@ -16,10 +16,15 @@ final class ImagePickerUtils { private static boolean isPermissionPresentInManifest(Context context, String permissionName) { try { PackageManager packageManager = context.getPackageManager(); - // TODO(stuartmorgan): Add new codepath: https://github.com/flutter/flutter/issues/121816 - @SuppressWarnings("deprecation") - PackageInfo packageInfo = - packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + PackageInfo packageInfo; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageInfo = + packageManager.getPackageInfo( + context.getPackageName(), + PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS)); + } else { + packageInfo = getPermissionsPackageInfoPreApi33(packageManager, context.getPackageName()); + } String[] requestedPermissions = packageInfo.requestedPermissions; return Arrays.asList(requestedPermissions).contains(permissionName); @@ -29,6 +34,13 @@ private static boolean isPermissionPresentInManifest(Context context, String per } } + @SuppressWarnings("deprecation") + private static PackageInfo getPermissionsPackageInfoPreApi33( + PackageManager packageManager, String packageName) + throws PackageManager.NameNotFoundException { + return packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); + } + /** * Camera permission need request if it present in manifest, because for M or great for take Photo * ar Video by intent need it permission, even if the camera permission is not used. diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 5e575a36b937..a567b6dbac51 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.9+3 +version: 0.8.9+5 environment: sdk: ^3.1.0 diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index bc80d9584d93..2cfa022e0b97 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 3.0.3 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Migrates package and tests to `package:web`. +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. ## 3.0.2 diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart index 94adce2411e4..a467a1895378 100644 --- a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart @@ -3,25 +3,29 @@ // found in the LICENSE file. import 'dart:convert'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart' as web; const String expectedStringContents = 'Hello, world!'; const String otherStringContents = 'Hello again, world!'; final Uint8List bytes = const Utf8Encoder().convert(expectedStringContents); final Uint8List otherBytes = const Utf8Encoder().convert(otherStringContents); -final Map options = { - 'type': 'text/plain', - 'lastModified': DateTime.utc(2017, 12, 13).millisecondsSinceEpoch, -}; -final html.File textFile = html.File([bytes], 'hello.txt', options); -final html.File secondTextFile = - html.File([otherBytes], 'secondFile.txt'); +// TODO(dit): When web:0.6.0 lands, move `type` to the [web.FilePropertyBag] constructor. +// See: https://github.com/dart-lang/web/pull/197 +final web.FilePropertyBag options = web.FilePropertyBag( + lastModified: DateTime.utc(2017, 12, 13).millisecondsSinceEpoch, +)..type = 'text/plain'; + +final web.File textFile = + web.File([bytes.toJS].toJS, 'hello.txt', options); +final web.File secondTextFile = + web.File([otherBytes.toJS].toJS, 'secondFile.txt'); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -36,12 +40,12 @@ void main() { testWidgets('getImageFromSource can select a file', ( WidgetTester _, ) async { - final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); - + final web.HTMLInputElement mockInput = web.HTMLInputElement() + ..type = 'file'; final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) - ..getMultipleFilesFromInput = ((_) => [textFile]); + ..getMultipleFilesFromInput = ((_) => [textFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); @@ -50,11 +54,12 @@ void main() { source: ImageSource.camera, ); - expect(html.querySelector('flt-image-picker-inputs')?.children.isEmpty, - isFalse); + expect( + web.document.querySelector('flt-image-picker-inputs')?.children.length, + isNonZero); // Mock the browser behavior of selecting a file... - mockInput.dispatchEvent(html.Event('change')); + mockInput.dispatchEvent(web.Event('change')); // Now the file should be available expect(image, completes); @@ -69,22 +74,24 @@ void main() { expect( file.lastModified(), completion( - DateTime.fromMillisecondsSinceEpoch(textFile.lastModified!), + DateTime.fromMillisecondsSinceEpoch(textFile.lastModified), )); - expect(html.querySelector('flt-image-picker-inputs')?.children.isEmpty, - isTrue); + expect( + web.document.querySelector('flt-image-picker-inputs')?.children.length, + isZero); }); testWidgets('getMultiImageWithOptions can select multiple files', ( WidgetTester _, ) async { - final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); + final web.HTMLInputElement mockInput = web.HTMLInputElement() + ..type = 'file'; final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) ..getMultipleFilesFromInput = - ((_) => [textFile, secondTextFile]); + ((_) => [textFile, secondTextFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); @@ -92,7 +99,7 @@ void main() { final Future> files = plugin.getMultiImageWithOptions(); // Mock the browser behavior of selecting a file... - mockInput.dispatchEvent(html.Event('change')); + mockInput.dispatchEvent(web.Event('change')); // Now the file should be available expect(files, completes); @@ -108,13 +115,14 @@ void main() { }); testWidgets('getMedia can select multiple files', (WidgetTester _) async { - final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); + final web.HTMLInputElement mockInput = web.HTMLInputElement() + ..type = 'file'; final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) ..getMultipleFilesFromInput = - ((_) => [textFile, secondTextFile]); + ((_) => [textFile, secondTextFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); @@ -123,7 +131,7 @@ void main() { plugin.getMedia(options: const MediaOptions(allowMultiple: true)); // Mock the browser behavior of selecting a file... - mockInput.dispatchEvent(html.Event('change')); + mockInput.dispatchEvent(web.Event('change')); // Now the file should be available expect(files, completes); @@ -139,20 +147,20 @@ void main() { }); group('cancel event', () { - late html.FileUploadInputElement mockInput; + late web.HTMLInputElement mockInput; late ImagePickerPluginTestOverrides overrides; late ImagePickerPlugin plugin; setUp(() { - mockInput = html.FileUploadInputElement(); + mockInput = web.HTMLInputElement()..type = 'file'; overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) - ..getMultipleFilesFromInput = ((_) => [textFile]); + ..getMultipleFilesFromInput = ((_) => [textFile]); plugin = ImagePickerPlugin(overrides: overrides); }); void mockCancel() { - mockInput.dispatchEvent(html.Event('cancel')); + mockInput.dispatchEvent(web.Event('cancel')); } testWidgets('getFiles - returns empty list', (WidgetTester _) async { @@ -226,61 +234,61 @@ void main() { group('createInputElement', () { testWidgets('accept: any, capture: null', (WidgetTester tester) async { - final html.Element input = plugin.createInputElement('any', null); + final web.Element input = plugin.createInputElement('any', null); - expect(input.attributes, containsPair('accept', 'any')); - expect(input.attributes, isNot(contains('capture'))); - expect(input.attributes, isNot(contains('multiple'))); + expect(input.getAttribute('accept'), 'any'); + expect(input.hasAttribute('capture'), false); + expect(input.hasAttribute('multiple'), false); }); testWidgets('accept: any, capture: something', (WidgetTester tester) async { - final html.Element input = plugin.createInputElement('any', 'something'); + final web.Element input = plugin.createInputElement('any', 'something'); - expect(input.attributes, containsPair('accept', 'any')); - expect(input.attributes, containsPair('capture', 'something')); - expect(input.attributes, isNot(contains('multiple'))); + expect(input.getAttribute('accept'), 'any'); + expect(input.getAttribute('capture'), 'something'); + expect(input.hasAttribute('multiple'), false); }); testWidgets('accept: any, capture: null, multi: true', (WidgetTester tester) async { - final html.Element input = + final web.Element input = plugin.createInputElement('any', null, multiple: true); - expect(input.attributes, containsPair('accept', 'any')); - expect(input.attributes, isNot(contains('capture'))); - expect(input.attributes, contains('multiple')); + expect(input.getAttribute('accept'), 'any'); + expect(input.hasAttribute('capture'), false); + expect(input.hasAttribute('multiple'), true); }); testWidgets('accept: any, capture: something, multi: true', (WidgetTester tester) async { - final html.Element input = + final web.Element input = plugin.createInputElement('any', 'something', multiple: true); - expect(input.attributes, containsPair('accept', 'any')); - expect(input.attributes, containsPair('capture', 'something')); - expect(input.attributes, contains('multiple')); + expect(input.getAttribute('accept'), 'any'); + expect(input.getAttribute('capture'), 'something'); + expect(input.hasAttribute('multiple'), true); }); }); group('Deprecated methods', () { - late html.FileUploadInputElement mockInput; + late web.HTMLInputElement mockInput; late ImagePickerPluginTestOverrides overrides; late ImagePickerPlugin plugin; setUp(() { - mockInput = html.FileUploadInputElement(); + mockInput = web.HTMLInputElement()..type = 'file'; overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) - ..getMultipleFilesFromInput = ((_) => [textFile]); + ..getMultipleFilesFromInput = ((_) => [textFile]); plugin = ImagePickerPlugin(overrides: overrides); }); void mockCancel() { - mockInput.dispatchEvent(html.Event('cancel')); + mockInput.dispatchEvent(web.Event('cancel')); } void mockChange() { - mockInput.dispatchEvent(html.Event('change')); + mockInput.dispatchEvent(web.Event('change')); } group('getImage', () { @@ -306,7 +314,7 @@ void main() { expect( file.lastModified(), completion( - DateTime.fromMillisecondsSinceEpoch(textFile.lastModified!), + DateTime.fromMillisecondsSinceEpoch(textFile.lastModified), )); }); @@ -326,7 +334,7 @@ void main() { testWidgets('can select multiple files', (WidgetTester _) async { // Override the returned files... overrides.getMultipleFilesFromInput = - (_) => [textFile, secondTextFile]; + (_) => [textFile, secondTextFile]; // ignore: deprecated_member_use final Future> files = plugin.getMultiImage(); diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart index 0ff6d2380004..dc01ef6d1972 100644 --- a/packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui'; @@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/src/image_resizer.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:web/helpers.dart'; +import 'package:web/web.dart' as web; //This is a sample 10x10 png image const String pngFileBase64Contents = @@ -24,14 +26,13 @@ void main() { late XFile pngFile; setUp(() { imageResizer = ImageResizer(); - final html.File pngHtmlFile = - _base64ToFile(pngFileBase64Contents, 'pngImage.png'); - pngFile = XFile(html.Url.createObjectUrl(pngHtmlFile), - name: pngHtmlFile.name, mimeType: pngHtmlFile.type); + final web.Blob pngHtmlFile = _base64ToBlob(pngFileBase64Contents); + pngFile = XFile(web.URL.createObjectURL(pngHtmlFile), + name: 'pngImage.png', mimeType: 'image/png'); }); testWidgets('image is loaded correctly ', (WidgetTester tester) async { - final html.ImageElement imageElement = + final web.HTMLImageElement imageElement = await imageResizer.loadImage(pngFile.path); expect(imageElement.width, 10); expect(imageElement.height, 10); @@ -40,9 +41,9 @@ void main() { testWidgets( "canvas is loaded with image's width and height when max width and max height are null", (WidgetTester widgetTester) async { - final html.ImageElement imageElement = + final web.HTMLImageElement imageElement = await imageResizer.loadImage(pngFile.path); - final html.CanvasElement canvas = + final web.HTMLCanvasElement canvas = imageResizer.resizeImageElement(imageElement, null, null); expect(canvas.width, imageElement.width); expect(canvas.height, imageElement.height); @@ -51,9 +52,9 @@ void main() { testWidgets( 'canvas size is scaled when max width and max height are not null', (WidgetTester widgetTester) async { - final html.ImageElement imageElement = + final web.HTMLImageElement imageElement = await imageResizer.loadImage(pngFile.path); - final html.CanvasElement canvas = + final web.HTMLCanvasElement canvas = imageResizer.resizeImageElement(imageElement, 8, 8); expect(canvas.width, 8); expect(canvas.height, 8); @@ -61,9 +62,9 @@ void main() { testWidgets('resized image is returned after converting canvas to file', (WidgetTester widgetTester) async { - final html.ImageElement imageElement = + final web.HTMLImageElement imageElement = await imageResizer.loadImage(pngFile.path); - final html.CanvasElement canvas = + final web.HTMLCanvasElement canvas = imageResizer.resizeImageElement(imageElement, null, null); final XFile resizedImage = await imageResizer.writeCanvasToFile(pngFile, canvas, null); @@ -112,19 +113,21 @@ void main() { Future _getImageSize(XFile file) async { final Completer completer = Completer(); - final html.ImageElement image = html.ImageElement(src: file.path); - image.onLoad.listen((html.Event event) { - completer.complete(Size(image.width!.toDouble(), image.height!.toDouble())); - }); - image.onError.listen((html.Event event) { - completer.complete(Size.zero); - }); + final web.HTMLImageElement image = web.HTMLImageElement(); + image + ..onLoad.listen((web.Event event) { + completer.complete(Size(image.width.toDouble(), image.height.toDouble())); + }) + ..onError.listen((web.Event event) { + completer.complete(Size.zero); + }) + ..src = file.path; return completer.future; } -html.File _base64ToFile(String data, String fileName) { +web.Blob _base64ToBlob(String data) { final List arr = data.split(','); - final String bstr = html.window.atob(arr[1]); + final String bstr = web.window.atob(arr[1]); int n = bstr.length; final Uint8List u8arr = Uint8List(n); @@ -133,5 +136,5 @@ html.File _base64ToFile(String data, String fileName) { n--; } - return html.File([u8arr], fileName); + return Blob([u8arr.toJS].toJS); } diff --git a/packages/image_picker/image_picker_for_web/example/pubspec.yaml b/packages/image_picker/image_picker_for_web/example/pubspec.yaml index 4e1a99bb5530..f4f85645c58d 100644 --- a/packages/image_picker/image_picker_for_web/example/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/example/pubspec.yaml @@ -2,8 +2,8 @@ name: image_picker_for_web_integration_tests publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: @@ -11,10 +11,10 @@ dependencies: image_picker_for_web: path: ../ image_picker_platform_interface: ^2.8.0 + web: ^0.5.1 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter - js: ^0.6.3 diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 315b7ddd4469..0241e0750a75 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -3,14 +3,16 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:mime/mime.dart' as mime; +import 'package:web/web.dart' as web; import 'src/image_resizer.dart'; +import 'src/pkg_web_tweaks.dart'; const String _kImagePickerInputsDomId = '__image_picker_web-file-input'; const String _kAcceptImageMimeType = 'image/*'; @@ -33,7 +35,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { bool get _hasOverrides => _overrides != null; - late html.Element _target; + late web.Element _target; late ImageResizer _imageResizer; @@ -151,14 +153,16 @@ class ImagePickerPlugin extends ImagePickerPlatform { String? capture, bool multiple = false, }) { - final html.FileUploadInputElement input = createInputElement( + final web.HTMLInputElement input = createInputElement( accept, capture, multiple: multiple, - ) as html.FileUploadInputElement; + ); _injectAndActivate(input); - return _getSelectedXFiles(input).whenComplete(input.remove); + return _getSelectedXFiles(input).whenComplete(() { + input.remove(); + }); } // Deprecated methods follow... @@ -226,51 +230,52 @@ class ImagePickerPlugin extends ImagePickerPlatform { return null; } - List? _getFilesFromInput(html.FileUploadInputElement input) { + List? _getFilesFromInput(web.HTMLInputElement input) { if (_hasOverrides) { return _overrides!.getMultipleFilesFromInput(input); } - return input.files; + return input.files?.toList; } /// Handles the OnChange event from a FileUploadInputElement object /// Returns a list of selected files. - List? _handleOnChangeEvent(html.Event event) { - final html.FileUploadInputElement? input = - event.target as html.FileUploadInputElement?; + List? _handleOnChangeEvent(web.Event event) { + final web.HTMLInputElement? input = event.target as web.HTMLInputElement?; return input == null ? null : _getFilesFromInput(input); } /// Monitors an and returns the selected file(s). - Future> _getSelectedXFiles(html.FileUploadInputElement input) { + Future> _getSelectedXFiles(web.HTMLInputElement input) { final Completer> completer = Completer>(); + // TODO(dit): Migrate all this to Streams (onChange, onError, onCancel) when onCancel is available. + // See: https://github.com/dart-lang/web/issues/199 // Observe the input until we can return something - input.onChange.first.then((html.Event event) { - final List? files = _handleOnChangeEvent(event); + input.onchange = (web.Event event) { + final List? files = _handleOnChangeEvent(event); if (!completer.isCompleted && files != null) { - completer.complete(files.map((html.File file) { + completer.complete(files.map((web.File file) { return XFile( - html.Url.createObjectUrl(file), + web.URL.createObjectURL(file), name: file.name, length: file.size, lastModified: DateTime.fromMillisecondsSinceEpoch( - file.lastModified ?? DateTime.now().millisecondsSinceEpoch, + file.lastModified, ), mimeType: file.type, ); }).toList()); } - }); + }.toJS; - input.addEventListener('cancel', (html.Event _) { + input.oncancel = (web.Event _) { completer.complete([]); - }); + }.toJS; - input.onError.first.then((html.Event event) { + input.onerror = (web.Event event) { if (!completer.isCompleted) { completer.completeError(event); } - }); + }.toJS; // Note that we don't bother detaching from these streams, since the // "input" gets re-created in the DOM every time the user needs to // pick a file. @@ -278,13 +283,13 @@ class ImagePickerPlugin extends ImagePickerPlatform { } /// Initializes a DOM container where we can host input elements. - html.Element _ensureInitialized(String id) { - html.Element? target = html.querySelector('#$id'); + web.Element _ensureInitialized(String id) { + web.Element? target = web.document.querySelector('#$id'); if (target == null) { - final html.Element targetElement = - html.Element.tag('flt-image-picker-inputs')..id = id; - - html.querySelector('body')!.children.add(targetElement); + final web.Element targetElement = + web.document.createElement('flt-image-picker-inputs')..id = id; + // TODO(ditman): Append inside the `view` of the running app. + web.document.body!.append(targetElement); target = targetElement; } return target; @@ -293,7 +298,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { /// Creates an input element that accepts certain file types, and /// allows to `capture` from the device's cameras (where supported) @visibleForTesting - html.Element createInputElement( + web.HTMLInputElement createInputElement( String? accept, String? capture, { bool multiple = false, @@ -302,10 +307,14 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _overrides!.createInputElement(accept, capture); } - final html.Element element = html.FileUploadInputElement() - ..accept = accept + final web.HTMLInputElement element = web.HTMLInputElement() + ..type = 'file' ..multiple = multiple; + if (accept != null) { + element.accept = accept; + } + if (capture != null) { element.setAttribute('capture', capture); } @@ -314,9 +323,9 @@ class ImagePickerPlugin extends ImagePickerPlatform { } /// Injects the file input element, and clicks on it - void _injectAndActivate(html.Element element) { - _target.children.clear(); - _target.children.add(element); + void _injectAndActivate(web.HTMLElement element) { + _target.replaceChildren([].toJS); + _target.append(element); // TODO(dit): Reimplement this with the showPicker() API, https://github.com/flutter/flutter/issues/130365 element.click(); } @@ -325,15 +334,15 @@ class ImagePickerPlugin extends ImagePickerPlatform { // Some tools to override behavior for unit-testing /// A function that creates a file input with the passed in `accept` and `capture` attributes. @visibleForTesting -typedef OverrideCreateInputFunction = html.Element Function( +typedef OverrideCreateInputFunction = web.HTMLInputElement Function( String? accept, String? capture, ); /// A function that extracts list of files from the file `input` passed in. @visibleForTesting -typedef OverrideExtractMultipleFilesFromInputFunction = List - Function(html.Element? input); +typedef OverrideExtractMultipleFilesFromInputFunction = List Function( + web.HTMLInputElement? input); /// Overrides for some of the functionality above. @visibleForTesting diff --git a/packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart b/packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart index 7cca935c6c91..7b32a451b67d 100644 --- a/packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart +++ b/packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart @@ -3,88 +3,107 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:math'; import 'dart:ui'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; +import 'package:web/helpers.dart'; +import 'package:web/web.dart' as web; import 'image_resizer_utils.dart'; /// Helper class that resizes images. class ImageResizer { /// Resizes the image if needed. + /// /// (Does not support gif images) - Future resizeImageIfNeeded(XFile file, double? maxWidth, - double? maxHeight, int? imageQuality) async { + Future resizeImageIfNeeded( + XFile file, + double? maxWidth, + double? maxHeight, + int? imageQuality, + ) async { if (!imageResizeNeeded(maxWidth, maxHeight, imageQuality) || file.mimeType == 'image/gif') { // Implement maxWidth and maxHeight for image/gif return file; } try { - final html.ImageElement imageElement = await loadImage(file.path); - final html.CanvasElement canvas = + final web.HTMLImageElement imageElement = await loadImage(file.path); + final web.HTMLCanvasElement canvas = resizeImageElement(imageElement, maxWidth, maxHeight); final XFile resizedImage = await writeCanvasToFile(file, canvas, imageQuality); - html.Url.revokeObjectUrl(file.path); + web.URL.revokeObjectURL(file.path); return resizedImage; } catch (e) { return file; } } - /// function that loads the blobUrl into an imageElement - Future loadImage(String blobUrl) { - final Completer imageLoadCompleter = - Completer(); - final html.ImageElement imageElement = html.ImageElement(); - // ignore: unsafe_html - imageElement.src = blobUrl; - - imageElement.onLoad.listen((html.Event event) { - imageLoadCompleter.complete(imageElement); - }); - imageElement.onError.listen((html.Event event) { - const String exception = 'Error while loading image.'; - imageElement.remove(); - imageLoadCompleter.completeError(exception); - }); + /// Loads the `blobUrl` into a [web.HTMLImageElement]. + Future loadImage(String blobUrl) { + final Completer imageLoadCompleter = + Completer(); + final web.HTMLImageElement imageElement = web.HTMLImageElement(); + imageElement + // ignore: unsafe_html + ..src = blobUrl + ..onLoad.listen((web.Event event) { + imageLoadCompleter.complete(imageElement); + }) + ..onError.listen((web.Event event) { + const String exception = 'Error while loading image.'; + imageElement.remove(); + imageLoadCompleter.completeError(exception); + }); return imageLoadCompleter.future; } - /// Draws image to a canvas while resizing the image to fit the [maxWidth],[maxHeight] constraints - html.CanvasElement resizeImageElement( - html.ImageElement source, double? maxWidth, double? maxHeight) { + /// Resizing the image in a canvas to fit the [maxWidth], [maxHeight] constraints. + web.HTMLCanvasElement resizeImageElement( + web.HTMLImageElement source, + double? maxWidth, + double? maxHeight, + ) { final Size newImageSize = calculateSizeOfDownScaledImage( - Size(source.width!.toDouble(), source.height!.toDouble()), + Size(source.width.toDouble(), source.height.toDouble()), maxWidth, maxHeight); - final html.CanvasElement canvas = html.CanvasElement(); - canvas.width = newImageSize.width.toInt(); - canvas.height = newImageSize.height.toInt(); - final html.CanvasRenderingContext2D context = canvas.context2D; + final web.HTMLCanvasElement canvas = web.HTMLCanvasElement() + ..width = newImageSize.width.toInt() + ..height = newImageSize.height.toInt(); + final web.CanvasRenderingContext2D context = canvas.context2D; if (maxHeight == null && maxWidth == null) { context.drawImage(source, 0, 0); } else { - context.drawImageScaled(source, 0, 0, canvas.width!, canvas.height!); + context.drawImageScaled( + source, 0, 0, canvas.width.toDouble(), canvas.height.toDouble()); } return canvas; } - /// function that converts a canvas element to Xfile + /// Converts a canvas element to [XFile]. + /// /// [imageQuality] is only supported for jpeg and webp images. Future writeCanvasToFile( - XFile originalFile, html.CanvasElement canvas, int? imageQuality) async { + XFile originalFile, + web.HTMLCanvasElement canvas, + int? imageQuality, + ) async { final double calculatedImageQuality = (min(imageQuality ?? 100, 100)) / 100.0; - final html.Blob blob = - await canvas.toBlob(originalFile.mimeType, calculatedImageQuality); - return XFile(html.Url.createObjectUrlFromBlob(blob), - mimeType: originalFile.mimeType, - name: 'scaled_${originalFile.name}', - lastModified: DateTime.now(), - length: blob.size); + final Completer completer = Completer(); + final web.BlobCallback blobCallback = (web.Blob blob) { + completer.complete(XFile(web.URL.createObjectURL(blob), + mimeType: originalFile.mimeType, + name: 'scaled_${originalFile.name}', + lastModified: DateTime.now(), + length: blob.size)); + }.toJS; + canvas.toBlob( + blobCallback, originalFile.mimeType ?? '', calculatedImageQuality.toJS); + return completer.future; } } diff --git a/packages/image_picker/image_picker_for_web/lib/src/pkg_web_tweaks.dart b/packages/image_picker/image_picker_for_web/lib/src/pkg_web_tweaks.dart new file mode 100644 index 000000000000..1152b01b2d4c --- /dev/null +++ b/packages/image_picker/image_picker_for_web/lib/src/pkg_web_tweaks.dart @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:web/web.dart' as web; + +/// Adds a `toList` method to [web.FileList] objects. +extension WebFileListToDartList on web.FileList { + /// Converts a [web.FileList] into a [List] of [web.File]. + /// + /// This method makes a copy. + List get toList => + [for (int i = 0; i < length; i++) item(i)!]; +} diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index b4394843b87e..85567a582094 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_for_web description: Web platform implementation of image_picker repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_for_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 3.0.2 +version: 3.0.3 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -23,6 +23,7 @@ dependencies: sdk: flutter image_picker_platform_interface: ^2.9.0 mime: ^1.0.4 + web: ^0.5.1 dev_dependencies: flutter_test: diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index d58ce03e7c9f..566aab0f686e 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.8.9+2 * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. +* Replaces deprecated UIGraphicsBeginImageContextWithOptions with UIGraphicsImageRenderer. ## 0.8.9+1 diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.m index b91eec293028..da07b71fd292 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.m @@ -15,59 +15,23 @@ + (NSData *)JPGTestData { // embedded in the test bundle. Fall back to the base64 string representation of the jpg. data = [[NSData alloc] initWithBase64EncodedString: - @"/9j/4AAQSkZJRgABAQAALgAuAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABA" - "AAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAAAuAAAAAQAAAC4AAAABAAOg" - "AQADAAAAAQABAACgAgAEAAAAAQAAAAygAwAEAAAAAQAAAAcAAAAA/+EJc2h0dHA6Ly9ucy5hZG9iZS5jb20" - "veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz" - "4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiP" - "iA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucy" - "MiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZ" - "G9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHBob3Rvc2hvcDpDcmVkaXQ9IsKpIEdvb2dsZSIvPiA8L3JkZjpSR" - "EY+IDwveDp4bXBtZXRhPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9In" - "ciPz4A/+0AVlBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAdHAFaAAMbJUccAgAAAgACHAJuAAnCqSBHb29nbG" - "UAOEJJTQQlAAAAAAAQmkt2IF3PgNJVMGnV2zijEf/AABEIAAcADAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQA" - "AAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQ" - "gjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h" - "5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp" - "6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAAB" - "AncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0R" - "FRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tr" - "e4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAQDAwMDAgQDAwMEBAQFBgoGBg" - "UFBgwICQcKDgwPDg4MDQ0PERYTDxAVEQ0NExoTFRcYGRkZDxIbHRsYHRYYGRj/2wBDAQQEBAYFBgsGBgsYEA0" - "QGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBj/3QAEAAH/2gAMAwEA" - "AhEDEQA/AMWiiivzk/qo/9k=" + @"/9j/4AAQSkZJRgABAQAASABIAAD/" + @"4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABA" + @"AIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAAygAw" + @"AEAAAAAQAAAAcAAAAA/8AAEQgABwAMAwERAAIRAQMRAf/" + @"EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//" + @"EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBka" + @"JSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio" + @"6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/" + @"EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//" + @"EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEX" + @"GBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZm" + @"qKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/" + @"bAEMAAgICAgICAwICAwUDAwMFBgUFBQUGCAYGBgYGCAoICAgICAgKCgoKCgoKCgwMDAwMDA4ODg4ODw8PDw8P" + @"Dw8PD//" + @"bAEMBAgMDBAQEBwQEBxALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ" + @"EBAQEP/dAAQAAv/aAAwDAQACEQMRAD8A9S8ZfsFeG/sH/IUboe7V/JHgR4t4v+2/4XVdj908aPHLG/2P/" + @"B6PsfO5/YL8O7m/4mjdT3av966fi1ivZw/ddF2P8VZ+OeN9pP8Ac9X2P//Z" options:0]; } return data; @@ -82,64 +46,14 @@ + (NSData *)JPGTallTestData { // embedded in the test bundle. Fall back to the base64 string representation of the jpg. data = [[NSData alloc] initWithBase64EncodedString: - @"/9j/4AAQSkZJRgABAQAALgAuAAD/" - @"4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABA" - @"AIAAIdpAAQAAAABAAAAWgAAAAAAAAAuAAAAAQAAAC4AAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAASgAw" - @"AEAAAAAQAAAAcAAAAA/" - @"+EJ12h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTX" - @"BDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB" - @"0az0iWE1QIENvcmUgNi4wLjAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkv" - @"MDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczpwaG90b" - @"3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC" - @"5vcmcvZGMvZWxlbWVudHMvMS4xLyIgcGhvdG9zaG9wOkNyZWRpdD0iwqkgR29vZ2xlIj4gPGRjOnN1YmplY3Q" - @"+IDxyZGY6QmFnLz4gPC9kYzpzdWJqZWN0PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1w" - @"bWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - @"CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - @"AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - @"gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - @"ICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+AP/" - @"tAFZQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAHRwBWgADGyVHHAIAAAIAAhwCbgAJwqkgR29vZ2xlADhCSU0E" - @"JQAAAAAAEJpLdiBdz4DSVTBp1ds4oxH/wAARCAAHAAQDASIAAhEBAxEB/" - @"8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/" - @"8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGR" - @"olJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqK" - @"jpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/" - @"8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/" - @"8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8R" - @"cYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJm" - @"aoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/" - @"9sAQwAEAwMDAwIEAwMDBAQEBQYKBgYFBQYMCAkHCg4MDw4ODA0NDxEWEw8QFRENDRMaExUXGBkZGQ8SGx0bGB" - @"0WGBkY/" - @"9sAQwEEBAQGBQYLBgYLGBANEBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGB" - @"gYGBgY/90ABAAB/9oADAMBAAIRAxEAPwDFooor85P6qP/Z" + @"/9j/4AAQSkZJRgABAQAAAAAAAAD/" + @"2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQE" + @"w8QEBD/" + @"2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE" + @"BAQEBD/wAARCAAHAAQDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/" + @"EAB0QAAECBwAAAAAAAAAAAAAAAAIABgQIFiVBQlH/xAAUAQEAAAAAAAAAAAAAAAAAAAAH/" + @"8QAHBEAAQQDAQAAAAAAAAAAAAAABAAHI1EIFlIX/9oADAMBAAIRAxEAPwA76kLbdSxV/" + @"PGxcTHjm7hXngUfVWgp+n0N3suLmqX/2Q==" options:0]; } return data; @@ -154,10 +68,26 @@ + (NSData *)PNGTestData { // embedded in the test bundle. Fall back to the base64 string representation of the png. data = [[NSData alloc] initWithBase64EncodedString: - @"iVBORw0KGgoAAAAEQ2dCSVAAIAYsuHdmAAAADUlIRFIAAAAMAAAABwgGAAAAPLKsJAAAAARnQU1BAACxjwv8Y" - "QUAAAABc1JHQgCuzhzpAAAAIGNIUk0AAHomAACAhAAA+" - "gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAJcEh" - "ZcwAABxMAAAcTAc4gDwgAAAAOSURBVGMwdX71nxTMMKqBCAwAsfuEYQAAAABJRU5ErkJggg==" + @"iVBORw0KGgoAAAANSUhEUgAAAAwAAAAHEAIAAADjQOcwAAAABGdBTUEAALGPC/" + @"xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAA" + @"FARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAA" + @"AAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADKADAAQAAAABAAAABwAAAACX5qZPA" + @"AAACXBIWXMAAAsTAAALEwEAmpwYAAACx2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bW" + @"xuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWx" + @"uczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRm" + @"OkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvY" + @"mUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leG" + @"lmLzEuMC8iPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICA" + @"gICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+" + @"MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+" + @"NzI8L3RpZmY6WFJlc29sdXRpb24+" + @"CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+" + @"CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xMjwvZXhpZjpQaXhlbFhEaW1lbnNpb24+" + @"CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+" + @"MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+" + @"NzwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+" + @"CjwveDp4bXBtZXRhPgqm8GvmAAAAUElEQVQYGWP4/58BCJDJV6sYGERD9wPFHRimhjIwZK3KAopMDXUAqtv/" + @"XxQoAlKBquf/fyaQEDUACwOD2W0qGeSlSiWDPFWoZBB1vEa1wAYAWgsa+7/A1uQAAAAASUVORK5CYII=" options:0]; } return data; diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m index ddd4087a9f4b..5566e1d92223 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m @@ -8,31 +8,106 @@ @import image_picker_ios.Test; @import XCTest; +// Corner colors of test image scaled to 3x2. Format is "R G B A". +static NSString *const kColorRepresentation3x2BottomLeftYellow = @"1 0.776471 0 1"; +static NSString *const kColorRepresentation3x2TopLeftRed = @"1 0.0666667 0 1"; +static NSString *const kColorRepresentation3x2BottomRightCyan = @"0 0.772549 1 1"; +static NSString *const kColorRepresentation3x2TopRightBlue = @"0 0.0705882 0.996078 1"; + @interface ImageUtilTests : XCTestCase @end @implementation ImageUtilTests +static NSString *ColorStringAtPixel(UIImage *image, int pixelX, int pixelY) { + CGImageRef cgImage = image.CGImage; + + uint32_t argb; + CGContextRef context1 = CGBitmapContextCreate( + &argb, 1, 1, CGImageGetBitsPerComponent(cgImage), CGImageGetBytesPerRow(cgImage), + CGColorSpaceCreateDeviceRGB(), CGImageGetBitmapInfo(cgImage)); + CGContextDrawImage( + context1, CGRectMake(-pixelX, -pixelY, CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)), + cgImage); + CGContextRelease(context1); + int blue = argb & 0xff; + int green = argb >> 8 & 0xff; + int red = argb >> 16 & 0xff; + int alpha = argb >> 24 & 0xff; + + return [CIColor colorWithRed:red / 255.f + green:green / 255.f + blue:blue / 255.f + alpha:alpha / 255.f] + .stringRepresentation; +} + +- (void)testScaledImage_EqualSizeReturnsSameImage { + UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; + UIImage *scaledImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:@(image.size.width) + maxHeight:@(image.size.height) + isMetadataAvailable:YES]; + + // Assert the same bytes pointer (not just equal objects). + XCTAssertEqual(image, scaledImage); +} + +- (void)testScaledImage_NilSizeReturnsSameImage { + UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; + UIImage *scaledImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:nil + maxHeight:nil + isMetadataAvailable:YES]; + + // Assert the same bytes pointer (not just equal objects). + XCTAssertEqual(image, scaledImage); +} + - (void)testScaledImage_ShouldBeScaled { UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; - UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image - maxWidth:@3 - maxHeight:@2 - isMetadataAvailable:YES]; - XCTAssertEqual(newImage.size.width, 3); - XCTAssertEqual(newImage.size.height, 2); + CGFloat scaledWidth = 3; + CGFloat scaledHeight = 2; + UIImage *scaledImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:@(scaledWidth) + maxHeight:@(scaledHeight) + isMetadataAvailable:YES]; + XCTAssertEqual(scaledImage.size.width, scaledWidth); + XCTAssertEqual(scaledImage.size.height, scaledHeight); + + // Check the corners to make sure nothing has been rotated. + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, 0, 0), + kColorRepresentation3x2BottomLeftYellow); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, 0, scaledHeight - 1), + kColorRepresentation3x2TopLeftRed); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, scaledWidth - 1, 0), + kColorRepresentation3x2BottomRightCyan); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, scaledWidth - 1, scaledHeight - 1), + kColorRepresentation3x2TopRightBlue); } - (void)testScaledImage_ShouldBeScaledWithNoMetadata { UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; - UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image - maxWidth:@3 - maxHeight:@2 - isMetadataAvailable:NO]; - XCTAssertEqual(newImage.size.width, 3); - XCTAssertEqual(newImage.size.height, 2); + CGFloat scaledWidth = 3; + CGFloat scaledHeight = 2; + UIImage *scaledImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:@(scaledWidth) + maxHeight:@(scaledHeight) + isMetadataAvailable:NO]; + XCTAssertEqual(scaledImage.size.width, scaledWidth); + XCTAssertEqual(scaledImage.size.height, scaledHeight); + + // Check the corners to make sure nothing has been rotated. + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, 0, 0), + kColorRepresentation3x2BottomLeftYellow); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, 0, scaledHeight - 1), + kColorRepresentation3x2TopLeftRed); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, scaledWidth - 1, 0), + kColorRepresentation3x2BottomRightCyan); + XCTAssertEqualObjects(ColorStringAtPixel(scaledImage, scaledWidth - 1, scaledHeight - 1), + kColorRepresentation3x2TopRightBlue); } - (void)testScaledImage_ShouldBeCorrectRotation { diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/bmpImage.bmp b/packages/image_picker/image_picker_ios/example/ios/TestImages/bmpImage.bmp index 553e765fb018..eda28bc0d024 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/bmpImage.bmp and b/packages/image_picker/image_picker_ios/example/ios/TestImages/bmpImage.bmp differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/heicImage.heic b/packages/image_picker/image_picker_ios/example/ios/TestImages/heicImage.heic index 03f41f69cc82..b8099f2f8c0b 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/heicImage.heic and b/packages/image_picker/image_picker_ios/example/ios/TestImages/heicImage.heic differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImage.jpg b/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImage.jpg index 12b2dc17624c..59997ece724f 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImage.jpg and b/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImage.jpg differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageTall.jpg b/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageTall.jpg index d7c137eb5ae1..a366ae66659f 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageTall.jpg and b/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageTall.jpg differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/pngImage.png b/packages/image_picker/image_picker_ios/example/ios/TestImages/pngImage.png index d7ad7d3968e9..724e5e2acb51 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/pngImage.png and b/packages/image_picker/image_picker_ios/example/ios/TestImages/pngImage.png differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/tiffImage.tiff b/packages/image_picker/image_picker_ios/example/ios/TestImages/tiffImage.tiff index 2431333c02e7..6d7cc0bb6b9f 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/tiffImage.tiff and b/packages/image_picker/image_picker_ios/example/ios/TestImages/tiffImage.tiff differ diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/webpImage.webp b/packages/image_picker/image_picker_ios/example/ios/TestImages/webpImage.webp index ab7d40d83968..9d595932460c 100644 Binary files a/packages/image_picker/image_picker_ios/example/ios/TestImages/webpImage.webp and b/packages/image_picker/image_picker_ios/example/ios/TestImages/webpImage.webp differ diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m index 245a66177930..547632871d17 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m @@ -28,16 +28,37 @@ - (instancetype)initWithImages:(NSArray *)images interval:(NSTimeInte @implementation FLTImagePickerImageUtil : NSObject +static UIImage *FLTImagePickerDrawScaledImage(UIImage *imageToScale, double width, double height) { + UIGraphicsImageRenderer *imageRenderer = + [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(width, height) + format:imageToScale.imageRendererFormat]; + return [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { + CGContextRef cgContext = rendererContext.CGContext; + + // Flip vertically to translate between UIKit and Quartz. + CGContextTranslateCTM(cgContext, 0, height); + CGContextScaleCTM(cgContext, 1, -1); + CGContextDrawImage(cgContext, CGRectMake(0, 0, width, height), imageToScale.CGImage); + }]; +} + + (UIImage *)scaledImage:(UIImage *)image maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight isMetadataAvailable:(BOOL)isMetadataAvailable { double originalWidth = image.size.width; double originalHeight = image.size.height; - double aspectRatio = originalWidth / originalHeight; - bool hasMaxWidth = maxWidth != nil; - bool hasMaxHeight = maxHeight != nil; + BOOL hasMaxWidth = maxWidth != nil; + BOOL hasMaxHeight = maxHeight != nil; + + if ((originalWidth == maxWidth.doubleValue && originalHeight == maxHeight.doubleValue) || + (!hasMaxWidth && !hasMaxHeight)) { + // Nothing to scale. + return image; + } + + double aspectRatio = originalWidth / originalHeight; double width = hasMaxWidth ? MIN(round([maxWidth doubleValue]), originalWidth) : originalWidth; double height = @@ -62,13 +83,7 @@ + (UIImage *)scaledImage:(UIImage *)image UIImage *imageToScale = [UIImage imageWithCGImage:image.CGImage scale:1 orientation:image.imageOrientation]; - - UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); - [imageToScale drawInRect:CGRectMake(0, 0, width, height)]; - - UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return scaledImage; + return FLTImagePickerDrawScaledImage(imageToScale, width, height); } // Scaling the image always rotate itself based on the current imageOrientation of the original @@ -91,13 +106,7 @@ + (UIImage *)scaledImage:(UIImage *)image width = height; height = temp; } - - UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); - [imageToScale drawInRect:CGRectMake(0, 0, width, height)]; - - UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return scaledImage; + return FLTImagePickerDrawScaledImage(imageToScale, width, height); } + (GIFInfo *)scaledGIFImage:(NSData *)data diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m index f699ca98c2dd..10081a8c13ec 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m @@ -31,12 +31,10 @@ - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result { @interface FLTImagePickerPlugin () -/** - * The UIImagePickerController instances that will be used when a new - * controller would normally be created. Each call to - * createImagePickerController will remove the current first element from - * the array. - */ +/// The UIImagePickerController instances that will be used when a new +/// controller would normally be created. Each call to +/// createImagePickerController will remove the current first element from +/// the array. @property(strong, nonatomic) NSMutableArray *imagePickerControllerOverrides; @@ -84,11 +82,9 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { return topController; } -/** - * Returns the UIImagePickerControllerCameraDevice to use given [source]. - * - * @param source The source specification from Dart. - */ +/// Returns the UIImagePickerControllerCameraDevice to use given [source]. +/// +/// @param source The source specification from Dart. - (UIImagePickerControllerCameraDevice)cameraDeviceForSource:(FLTSourceSpecification *)source { switch (source.camera) { case FLTSourceCameraFront: @@ -287,12 +283,10 @@ - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source #pragma mark - -/** - * If a call is still in progress, cancels it by returning an error and then clearing state. - * - * TODO(stuartmorgan): Eliminate this, and instead track context per image picker (e.g., using - * associated objects). - */ +/// If a call is still in progress, cancels it by returning an error and then clearing state. +/// +/// TODO(stuartmorgan): Eliminate this, and instead track context per image picker (e.g., using +/// associated objects). - (void)cancelInProgressCall { if (self.callContext) { [self sendCallResultWithError:[FlutterError errorWithCode:@"multiple_request" @@ -687,12 +681,10 @@ - (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList { self.callContext = nil; } -/** - * Sends the given error via `callContext.result` as the result of the original platform channel - * method call, clearing the in-progress call state. - * - * @param error The error to return. - */ +/// Sends the given error via `callContext.result` as the result of the original platform channel +/// method call, clearing the in-progress call state. +/// +/// @param error The error to return. - (void)sendCallResultWithError:(FlutterError *)error { if (!self.callContext) { return; diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h index 99d3ef6e195b..845cdcf878a9 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h @@ -10,95 +10,77 @@ NS_ASSUME_NONNULL_BEGIN -/** - * The return handler used for all method calls, which internally adapts the provided result list - * to return either a list or a single element depending on the original call. - */ +/// The return handler used for all method calls, which internally adapts the provided result list +/// to return either a list or a single element depending on the original call. typedef void (^FlutterResultAdapter)(NSArray *_Nullable, FlutterError *_Nullable); -/** - * A container class for context to use when handling a method call from the Dart side. - */ +/// A container class for context to use when handling a method call from the Dart side. @interface FLTImagePickerMethodCallContext : NSObject -/** - * Initializes a new context that calls |result| on completion of the operation. - */ +/// Initializes a new context that calls |result| on completion of the operation. - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result; -/** The callback to provide results to the Dart caller. */ +/// The callback to provide results to the Dart caller. @property(nonatomic, copy, nonnull) FlutterResultAdapter result; -/** - * The maximum size to enforce on the results. - * - * If nil, no resizing is done. - */ +/// The maximum size to enforce on the results. +/// +/// If nil, no resizing is done. @property(nonatomic, strong, nullable) FLTMaxSize *maxSize; -/** - * The image quality to resample the results to. - * - * If nil, no resampling is done. - */ +/// The image quality to resample the results to. +/// +/// If nil, no resampling is done. @property(nonatomic, strong, nullable) NSNumber *imageQuality; -/** Maximum number of images to select. 0 indicates no maximum. */ +/// Maximum number of images to select. 0 indicates no maximum. @property(nonatomic, assign) int maxImageCount; -/** Whether the image should be picked with full metadata (requires gallery permissions) */ +/// Whether the image should be picked with full metadata (requires gallery permissions) @property(nonatomic, assign) BOOL requestFullMetadata; -/** Whether the picker should include videos in the list*/ +/// Whether the picker should include videos in the list*/ @property(nonatomic, assign) BOOL includeVideo; @end #pragma mark - -/** Methods exposed for unit testing. */ +/// Methods exposed for unit testing. @interface FLTImagePickerPlugin () -/** - * The context of the Flutter method call that is currently being handled, if any. - */ +/// The context of the Flutter method call that is currently being handled, if any. @property(strong, nonatomic, nullable) FLTImagePickerMethodCallContext *callContext; - (UIViewController *)viewControllerWithWindow:(nullable UIWindow *)window; -/** - * Validates the provided paths list, then sends it via `callContext.result` as the result of the - * original platform channel method call, clearing the in-progress call state. - * - * @param pathList The paths to return. nil indicates a cancelled operation. - */ +/// Validates the provided paths list, then sends it via `callContext.result` as the result of the +/// original platform channel method call, clearing the in-progress call state. +/// +/// @param pathList The paths to return. nil indicates a cancelled operation. - (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList; -/** - * Tells the delegate that the user cancelled the pick operation. - * - * Your delegate’s implementation of this method should dismiss the picker view - * by calling the dismissModalViewControllerAnimated: method of the parent - * view controller. - * - * Implementation of this method is optional, but expected. - * - * @param picker The controller object managing the image picker interface. - */ +/// Tells the delegate that the user cancelled the pick operation. +/// +/// Your delegate’s implementation of this method should dismiss the picker view +/// by calling the dismissModalViewControllerAnimated: method of the parent +/// view controller. +/// +/// Implementation of this method is optional, but expected. +/// +/// @param picker The controller object managing the image picker interface. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; -/** - * Sets UIImagePickerController instances that will be used when a new - * controller would normally be created. Each call to - * createImagePickerController will remove the current first element from - * the array. - * - * Should be used for testing purposes only. - */ +/// Sets UIImagePickerController instances that will be used when a new +/// controller would normally be created. Each call to +/// createImagePickerController will remove the current first element from +/// the array. +/// +/// Should be used for testing purposes only. - (void)setImagePickerControllerOverrides: (NSArray *)imagePickerControllers; diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h index 00c1f1dacd6c..dcafc42efe49 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h @@ -14,17 +14,15 @@ NS_ASSUME_NONNULL_BEGIN /// Returns either the saved path, or an error. Both cannot be set. typedef void (^FLTGetSavedPath)(NSString *_Nullable savedPath, FlutterError *_Nullable error); -/*! - @class FLTPHPickerSaveImageToPathOperation - - @brief The FLTPHPickerSaveImageToPathOperation class - - @discussion This class was implemented to handle saved image paths and populate the pathList - with the final result by using GetSavedPath type block. - - @superclass SuperClass: NSOperation\n - @helps It helps FLTImagePickerPlugin class. - */ +/// @class FLTPHPickerSaveImageToPathOperation +/// +/// @brief The FLTPHPickerSaveImageToPathOperation class +/// +/// @discussion This class was implemented to handle saved image paths and populate the pathList +/// with the final result by using GetSavedPath type block. +/// +/// @superclass SuperClass: NSOperation\n +/// @helps It helps FLTImagePickerPlugin class. @interface FLTPHPickerSaveImageToPathOperation : NSOperation - (instancetype)initWithResult:(PHPickerResult *)result diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m index 3476721ae615..bf3922ae7d3d 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m @@ -124,9 +124,7 @@ - (void)start { } } -/** - * Processes the image. - */ +/// Processes the image. - (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { UIImage *localImage = [[UIImage alloc] initWithData:pickerImageData]; @@ -190,9 +188,7 @@ - (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { } } -/** - * Processes the video. - */ +/// Processes the video. - (void)processVideo API_AVAILABLE(ios(14)) { NSString *typeIdentifier = self.result.itemProvider.registeredTypeIdentifiers.firstObject; [self.result.itemProvider diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 46e57768fea5..c1b3c8d62217 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.9+1 +version: 0.8.9+2 environment: sdk: ^3.2.3 diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 854437f5d232..ef63d598bb83 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.9.4 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. +* Removes a few deprecated API usages. ## 2.9.3 diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart index 7d9761a57602..e296ceb9d80d 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart @@ -27,7 +27,7 @@ class PickedFile extends PickedFileBase { Future get _bytes async { if (_initBytes != null) { - return Future.value(UnmodifiableUint8ListView(_initBytes!)); + return _initBytes.asUnmodifiableView(); } return http.readBytes(Uri.parse(path)); } diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index 66b17ed51f47..b38945c6dac7 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/packages/tree/main/packages/image_picker/ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%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: 2.9.3 +version: 2.9.4 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: cross_file: ^0.3.1+1 diff --git a/packages/image_picker/image_picker_windows/example/windows/flutter/CMakeLists.txt b/packages/image_picker/image_picker_windows/example/windows/flutter/CMakeLists.txt index b2e4bd8d658b..4f2af69bbb09 100644 --- a/packages/image_picker/image_picker_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/image_picker/image_picker_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/image_picker/image_picker_windows/example/windows/runner/Runner.rc b/packages/image_picker/image_picker_windows/example/windows/runner/Runner.rc index 5fdea291cf19..0f5c0857111f 100644 --- a/packages/image_picker/image_picker_windows/example/windows/runner/Runner.rc +++ b/packages/image_picker/image_picker_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle index 9e1388cb73bb..af2658da94d3 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle @@ -110,7 +110,7 @@ dependencies { implementation 'com.android.billingclient:billing:3.0.2' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.0.0' - testImplementation 'org.json:json:20240205' + testImplementation 'org.json:json:20240303' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 896e9f958995..223d0441a788 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.3.2 +* Adds UserChoiceBilling APIs to platform addition. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 0.3.1 diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index 9324c9367ee8..10ce08d63fc8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -6,7 +6,9 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.UserChoiceBillingListener; import io.flutter.plugin.common.MethodChannel; /** Responsible for creating a {@link BillingClient} object. */ @@ -22,5 +24,8 @@ interface BillingClientFactory { * @return The {@link BillingClient} object that is created. */ BillingClient createBillingClient( - @NonNull Context context, @NonNull MethodChannel channel, int billingChoiceMode); + @NonNull Context context, + @NonNull MethodChannel channel, + int billingChoiceMode, + @Nullable UserChoiceBillingListener userChoiceBillingListener); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java index c6911f8314a3..4c53951a495a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java @@ -6,7 +6,10 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.UserChoiceBillingListener; +import io.flutter.Log; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.BillingChoiceMode; @@ -15,11 +18,34 @@ final class BillingClientFactoryImpl implements BillingClientFactory { @Override public BillingClient createBillingClient( - @NonNull Context context, @NonNull MethodChannel channel, int billingChoiceMode) { + @NonNull Context context, + @NonNull MethodChannel channel, + int billingChoiceMode, + @Nullable UserChoiceBillingListener userChoiceBillingListener) { BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases(); - if (billingChoiceMode == BillingChoiceMode.ALTERNATIVE_BILLING_ONLY) { - // https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app - builder.enableAlternativeBillingOnly(); + switch (billingChoiceMode) { + case BillingChoiceMode.ALTERNATIVE_BILLING_ONLY: + // https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app + builder.enableAlternativeBillingOnly(); + break; + case BillingChoiceMode.USER_CHOICE_BILLING: + if (userChoiceBillingListener != null) { + // https://developer.android.com/google/play/billing/alternative/alternative-billing-with-user-choice-in-app + builder.enableUserChoiceBilling(userChoiceBillingListener); + } else { + Log.e( + "BillingClientFactoryImpl", + "userChoiceBillingListener null when USER_CHOICE_BILLING set. Defaulting to PLAY_BILLING_ONLY"); + } + break; + case BillingChoiceMode.PLAY_BILLING_ONLY: + // Do nothing. + break; + default: + Log.e( + "BillingClientFactoryImpl", + "Unknown BillingChoiceMode " + billingChoiceMode + ", Defaulting to PLAY_BILLING_ONLY"); + break; } return builder.setListener(new PluginPurchaseListener(channel)).build(); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index ecf88747779a..0343a8a9d40b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -10,6 +10,7 @@ import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; +import static io.flutter.plugins.inapppurchase.Translator.fromUserChoiceDetails; import static io.flutter.plugins.inapppurchase.Translator.toProductList; import android.app.Activity; @@ -33,6 +34,7 @@ import com.android.billingclient.api.QueryProductDetailsParams.Product; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; +import com.android.billingclient.api.UserChoiceBillingListener; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.ArrayList; @@ -72,6 +74,8 @@ static final class MethodNames { "BillingClient#createAlternativeBillingOnlyReportingDetails()"; static final String SHOW_ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG = "BillingClient#showAlternativeBillingOnlyInformationDialog()"; + static final String USER_SELECTED_ALTERNATIVE_BILLING = + "UserChoiceBillingListener#userSelectedAlternativeBilling(UserChoiceDetails)"; private MethodNames() {} } @@ -94,6 +98,7 @@ private MethodArgs() {} static final class BillingChoiceMode { static final int PLAY_BILLING_ONLY = 0; static final int ALTERNATIVE_BILLING_ONLY = 1; + static final int USER_CHOICE_BILLING = 2; } // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new @@ -507,9 +512,10 @@ private void getConnectionState(final MethodChannel.Result result) { private void startConnection( final int handle, final MethodChannel.Result result, int billingChoiceMode) { if (billingClient == null) { + UserChoiceBillingListener listener = getUserChoiceBillingListener(billingChoiceMode); billingClient = billingClientFactory.createBillingClient( - applicationContext, methodChannel, billingChoiceMode); + applicationContext, methodChannel, billingChoiceMode, listener); } billingClient.startConnection( @@ -537,6 +543,19 @@ public void onBillingServiceDisconnected() { }); } + @Nullable + private UserChoiceBillingListener getUserChoiceBillingListener(int billingChoiceMode) { + UserChoiceBillingListener listener = null; + if (billingChoiceMode == BillingChoiceMode.USER_CHOICE_BILLING) { + listener = + userChoiceDetails -> { + final Map arguments = fromUserChoiceDetails(userChoiceDetails); + methodChannel.invokeMethod(MethodNames.USER_SELECTED_ALTERNATIVE_BILLING, arguments); + }; + } + return listener; + } + private void acknowledgePurchase(String purchaseToken, final MethodChannel.Result result) { if (billingClientError(result)) { return; diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index 8c80b1797eda..f9e91659bc23 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -14,6 +14,8 @@ import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.UserChoiceDetails; +import com.android.billingclient.api.UserChoiceDetails.Product; import java.util.ArrayList; import java.util.Collections; import java.util.Currency; @@ -233,6 +235,34 @@ static HashMap fromBillingResult(BillingResult billingResult) { return info; } + static HashMap fromUserChoiceDetails(UserChoiceDetails userChoiceDetails) { + HashMap info = new HashMap<>(); + info.put("externalTransactionToken", userChoiceDetails.getExternalTransactionToken()); + info.put("originalExternalTransactionId", userChoiceDetails.getOriginalExternalTransactionId()); + info.put("products", fromProductsList(userChoiceDetails.getProducts())); + return info; + } + + static List> fromProductsList(List productsList) { + if (productsList.isEmpty()) { + return Collections.emptyList(); + } + ArrayList> output = new ArrayList<>(); + for (Product product : productsList) { + output.add(fromProduct(product)); + } + return output; + } + + static HashMap fromProduct(Product product) { + HashMap info = new HashMap<>(); + info.put("id", product.getId()); + info.put("offerToken", product.getOfferToken()); + info.put("productType", product.getType()); + + return info; + } + /** Converter from {@link BillingResult} and {@link BillingConfig} to map. */ static HashMap fromBillingConfig( BillingResult result, BillingConfig billingConfig) { diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index bfdf928ca511..42acbf5f8642 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -20,6 +20,7 @@ import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.SHOW_ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.START_CONNECTION; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.USER_SELECTED_ALTERNATIVE_BILLING; import static io.flutter.plugins.inapppurchase.PluginPurchaseListener.ON_PURCHASES_UPDATED; import static io.flutter.plugins.inapppurchase.Translator.fromAlternativeBillingOnlyReportingDetails; import static io.flutter.plugins.inapppurchase.Translator.fromBillingConfig; @@ -27,6 +28,7 @@ import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; +import static io.flutter.plugins.inapppurchase.Translator.fromUserChoiceDetails; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; @@ -73,6 +75,8 @@ import com.android.billingclient.api.QueryProductDetailsParams; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; +import com.android.billingclient.api.UserChoiceBillingListener; +import com.android.billingclient.api.UserChoiceDetails; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -82,6 +86,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -92,6 +97,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.stubbing.Answer; @@ -107,15 +113,23 @@ public class MethodCallHandlerTest { @Mock ActivityPluginBinding mockActivityPluginBinding; @Captor ArgumentCaptor> resultCaptor; + private final int DEFAULT_HANDLE = 1; + @Before public void setUp() { MockitoAnnotations.openMocks(this); // Use the same client no matter if alternative billing is enabled or not. when(factory.createBillingClient( - context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY)) + context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY, null)) + .thenReturn(mockBillingClient); + when(factory.createBillingClient( + context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY, null)) .thenReturn(mockBillingClient); when(factory.createBillingClient( - context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY)) + any(Context.class), + any(MethodChannel.class), + eq(BillingChoiceMode.USER_CHOICE_BILLING), + any(UserChoiceBillingListener.class))) .thenReturn(mockBillingClient); methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory); when(mockActivityPluginBinding.getActivity()).thenReturn(activity); @@ -164,7 +178,7 @@ public void startConnection() { mockStartConnection(BillingChoiceMode.PLAY_BILLING_ONLY); verify(result, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY, null); BillingResult billingResult = BillingResult.newBuilder() @@ -183,7 +197,7 @@ public void startConnectionAlternativeBillingOnly() { verify(result, never()).success(any()); verify(factory, times(1)) .createBillingClient( - context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY, null); BillingResult billingResult = BillingResult.newBuilder() @@ -209,7 +223,7 @@ public void startConnectionAlternativeBillingUnset() { methodChannelHandler.onMethodCall(call, result); verify(result, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY, null); BillingResult billingResult = BillingResult.newBuilder() @@ -221,6 +235,106 @@ public void startConnectionAlternativeBillingUnset() { verify(result, times(1)).success(fromBillingResult(billingResult)); } + @Test + public void startConnectionUserChoiceBilling() { + ArgumentCaptor captor = + mockStartConnection(BillingChoiceMode.USER_CHOICE_BILLING); + ArgumentCaptor billingCaptor = + ArgumentCaptor.forClass(UserChoiceBillingListener.class); + verify(result, never()).success(any()); + verify(factory, times(1)) + .createBillingClient( + any(Context.class), + any(MethodChannel.class), + eq(BillingChoiceMode.USER_CHOICE_BILLING), + billingCaptor.capture()); + + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + captor.getValue().onBillingSetupFinished(billingResult); + + verify(result, times(1)).success(fromBillingResult(billingResult)); + UserChoiceDetails details = mock(UserChoiceDetails.class); + final String externalTransactionToken = "someLongTokenId1234"; + final String originalTransactionId = "originalTransactionId123456"; + when(details.getExternalTransactionToken()).thenReturn(externalTransactionToken); + when(details.getOriginalExternalTransactionId()).thenReturn(originalTransactionId); + when(details.getProducts()).thenReturn(Collections.emptyList()); + billingCaptor.getValue().userSelectedAlternativeBilling(details); + + verify(mockMethodChannel, times(1)) + .invokeMethod(USER_SELECTED_ALTERNATIVE_BILLING, fromUserChoiceDetails(details)); + } + + @Test + public void userChoiceBillingOnSecondConnection() { + // First connection. + ArgumentCaptor captor1 = + mockStartConnection(BillingChoiceMode.PLAY_BILLING_ONLY); + verify(result, never()).success(any()); + verify(factory, times(1)) + .createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY, null); + + BillingResult billingResult1 = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + final BillingClientStateListener stateListener = captor1.getValue(); + stateListener.onBillingSetupFinished(billingResult1); + verify(result, times(1)).success(fromBillingResult(billingResult1)); + Mockito.reset(result, mockMethodChannel, mockBillingClient); + + // Disconnect + MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); + methodChannelHandler.onMethodCall(disconnectCall, result); + + // Verify that the client is disconnected and that the OnDisconnect callback has + // been triggered + verify(result, times(1)).success(any()); + verify(mockBillingClient, times(1)).endConnection(); + stateListener.onBillingServiceDisconnected(); + Map expectedInvocation = new HashMap<>(); + expectedInvocation.put("handle", DEFAULT_HANDLE); + verify(mockMethodChannel, times(1)).invokeMethod(ON_DISCONNECT, expectedInvocation); + Mockito.reset(result, mockMethodChannel, mockBillingClient); + + // Second connection. + ArgumentCaptor captor2 = + mockStartConnection(BillingChoiceMode.USER_CHOICE_BILLING); + ArgumentCaptor billingCaptor = + ArgumentCaptor.forClass(UserChoiceBillingListener.class); + verify(result, never()).success(any()); + verify(factory, times(1)) + .createBillingClient( + any(Context.class), + any(MethodChannel.class), + eq(BillingChoiceMode.USER_CHOICE_BILLING), + billingCaptor.capture()); + + BillingResult billingResult2 = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + captor2.getValue().onBillingSetupFinished(billingResult2); + + verify(result, times(1)).success(fromBillingResult(billingResult2)); + UserChoiceDetails details = mock(UserChoiceDetails.class); + final String externalTransactionToken = "someLongTokenId1234"; + final String originalTransactionId = "originalTransactionId123456"; + when(details.getExternalTransactionToken()).thenReturn(externalTransactionToken); + when(details.getOriginalExternalTransactionId()).thenReturn(originalTransactionId); + when(details.getProducts()).thenReturn(Collections.emptyList()); + billingCaptor.getValue().userSelectedAlternativeBilling(details); + + verify(mockMethodChannel, times(1)) + .invokeMethod(USER_SELECTED_ALTERNATIVE_BILLING, fromUserChoiceDetails(details)); + } + @Test public void startConnection_multipleCalls() { Map arguments = new HashMap<>(); @@ -1071,7 +1185,7 @@ private ArgumentCaptor mockStartConnection() { */ private ArgumentCaptor mockStartConnection(int billingChoiceMode) { Map arguments = new HashMap<>(); - arguments.put(MethodArgs.HANDLE, 1); + arguments.put(MethodArgs.HANDLE, DEFAULT_HANDLE); arguments.put(MethodArgs.BILLING_CHOICE_MODE, billingChoiceMode); MethodCall call = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle index dfccaac37d6e..9279ec8b2520 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle @@ -110,7 +110,7 @@ dependencies { implementation 'com.android.billingclient:billing:6.1.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' - testImplementation 'org.json:json:20231013' + testImplementation 'org.json:json:20240303' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart index 568becd7188c..8ed5de2ce4b6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart @@ -27,7 +27,8 @@ void main() { late final BillingClient billingClient; setUpAll(() { - billingClient = BillingClient((PurchasesResultWrapper _) {}); + billingClient = BillingClient( + (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}); }); testWidgets('BillingClient.acknowledgePurchase', diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index fbae24a5dcfd..89883a1564ad 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -44,6 +44,7 @@ class _MyAppState extends State<_MyApp> { final InAppPurchasePlatform _inAppPurchasePlatform = InAppPurchasePlatform.instance; late StreamSubscription> _subscription; + late StreamSubscription _userChoiceDetailsStream; List _notFoundIds = []; List _products = []; List _purchases = []; @@ -56,6 +57,7 @@ class _MyAppState extends State<_MyApp> { bool _purchasePending = false; bool _loading = true; String? _queryProductError; + final List _userChoiceDetailsList = []; @override void initState() { @@ -70,6 +72,19 @@ class _MyAppState extends State<_MyApp> { // handle error here. }); initStoreInfo(); + final InAppPurchaseAndroidPlatformAddition addition = + InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition; + final Stream userChoiceDetailsUpdated = + addition.userChoiceDetailsStream; + _userChoiceDetailsStream = + userChoiceDetailsUpdated.listen((GooglePlayUserChoiceDetails details) { + deliverUserChoiceDetails(details); + }, onDone: () { + _userChoiceDetailsStream.cancel(); + }, onError: (Object error) { + // handle error here. + }); super.initState(); } @@ -134,6 +149,8 @@ class _MyAppState extends State<_MyApp> { @override void dispose() { _subscription.cancel(); + _userChoiceDetailsStream.cancel(); + _userChoiceDetailsList.clear(); super.dispose(); } @@ -149,6 +166,7 @@ class _MyAppState extends State<_MyApp> { _buildConsumableBox(), const _FeatureCard(), _buildFetchButtons(), + _buildUserChoiceDetailsDisplay(), ], ), ); @@ -326,6 +344,26 @@ class _MyAppState extends State<_MyApp> { ); } + Card _buildUserChoiceDetailsDisplay() { + const ListTile header = ListTile(title: Text('UserChoiceDetails')); + final List entries = []; + for (final String item in _userChoiceDetailsList) { + entries.add(ListTile( + title: Text(item, + style: TextStyle(color: ThemeData.light().colorScheme.primary)), + subtitle: Text(_countryCode))); + } + return Card( + child: Column( + children: [ + header, + const Divider(), + ...entries, + ], + ), + ); + } + Card _buildProductList() { if (_loading) { return const Card( @@ -537,6 +575,15 @@ class _MyAppState extends State<_MyApp> { // handle invalid purchase here if _verifyPurchase` failed. } + Future deliverUserChoiceDetails( + GooglePlayUserChoiceDetails details) async { + final String detailDescription = + '${details.externalTransactionToken}, ${details.originalExternalTransactionId}, ${details.products.length}'; + setState(() { + _userChoiceDetailsList.add(detailDescription); + }); + } + Future _listenToPurchaseUpdated( List purchaseDetailsList) async { for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart index 8a53b95e9a7e..508b80e05f7d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart @@ -11,3 +11,4 @@ export 'src/billing_client_wrappers/product_details_wrapper.dart'; export 'src/billing_client_wrappers/product_wrapper.dart'; export 'src/billing_client_wrappers/purchase_wrapper.dart'; export 'src/billing_client_wrappers/subscription_offer_details_wrapper.dart'; +export 'src/billing_client_wrappers/user_choice_details_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 789ba5e01cc3..e9a3b50ce5cb 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; import 'purchase_wrapper.dart'; +import 'user_choice_details_wrapper.dart'; /// Abstraction of result of [BillingClient] operation that includes /// a [BillingResponse]. @@ -37,6 +38,13 @@ class BillingClientManager { _connect(); } + /// Stream of `userSelectedAlternativeBilling` events from the [BillingClient]. + /// + /// This is a broadcast stream, so it can be listened to multiple times. + /// A "done" event will be sent after [dispose] is called. + late final Stream userChoiceDetailsStream = + _userChoiceAlternativeBillingController.stream; + /// Stream of `onPurchasesUpdated` events from the [BillingClient]. /// /// This is a broadcast stream, so it can be listened to multiple times. @@ -49,10 +57,14 @@ class BillingClientManager { /// In order to access the [BillingClient], use [runWithClient] /// and [runWithClientNonRetryable] methods. @visibleForTesting - late final BillingClient client = BillingClient(_onPurchasesUpdated); + late final BillingClient client = + BillingClient(_onPurchasesUpdated, onUserChoiceAlternativeBilling); final StreamController _purchasesUpdatedController = StreamController.broadcast(); + final StreamController + _userChoiceAlternativeBillingController = + StreamController.broadcast(); BillingChoiceMode _billingChoiceMode; bool _isConnecting = false; @@ -113,12 +125,14 @@ class BillingClientManager { /// After calling [dispose]: /// - Further connection attempts will not be made. /// - [purchasesUpdatedStream] will be closed. + /// - [userChoiceDetailsStream] will be closed. /// - Calls to [runWithClient] and [runWithClientNonRetryable] will throw. void dispose() { _debugAssertNotDisposed(); _isDisposed = true; client.endConnection(); _purchasesUpdatedController.close(); + _userChoiceAlternativeBillingController.close(); } /// Ends connection to [BillingClient] and reconnects with [billingChoiceMode]. @@ -168,4 +182,14 @@ class BillingClientManager { 'called dispose() on a BillingClientManager, it can no longer be used.', ); } + + /// Callback passed to [BillingClient] to use when customer chooses + /// alternative billing. + @visibleForTesting + void onUserChoiceAlternativeBilling(UserChoiceDetailsWrapper event) { + if (_isDisposed) { + return; + } + _userChoiceAlternativeBillingController.add(event); + } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index 15dc4217fe69..b3684d8177f4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -18,6 +18,12 @@ part 'billing_client_wrapper.g.dart'; @visibleForTesting const String kOnPurchasesUpdated = 'PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)'; + +/// Method identifier for the userSelectedAlternativeBilling method channel method. +@visibleForTesting +const String kUserSelectedAlternativeBilling = + 'UserChoiceBillingListener#userSelectedAlternativeBilling(UserChoiceDetails)'; + const String _kOnBillingServiceDisconnected = 'BillingClientStateListener#onBillingServiceDisconnected()'; @@ -40,6 +46,10 @@ const String _kOnBillingServiceDisconnected = typedef PurchasesUpdatedListener = void Function( PurchasesResultWrapper purchasesResult); +/// Wraps a [UserChoiceBillingListener](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceBillingListener) +typedef UserSelectedAlternativeBillingListener = void Function( + UserChoiceDetailsWrapper userChoiceDetailsWrapper); + /// This class can be used directly instead of [InAppPurchaseConnection] to call /// Play-specific billing APIs. /// @@ -60,11 +70,16 @@ typedef PurchasesUpdatedListener = void Function( /// transparently. class BillingClient { /// Creates a billing client. - BillingClient(PurchasesUpdatedListener onPurchasesUpdated) { + BillingClient(PurchasesUpdatedListener onPurchasesUpdated, + UserSelectedAlternativeBillingListener? alternativeBillingListener) { channel.setMethodCallHandler(callHandler); _callbacks[kOnPurchasesUpdated] = [ onPurchasesUpdated ]; + _callbacks[kUserSelectedAlternativeBilling] = alternativeBillingListener == + null + ? [] + : [alternativeBillingListener]; } // Occasionally methods in the native layer require a Dart callback to be @@ -114,7 +129,8 @@ class BillingClient { BillingChoiceMode.playBillingOnly}) async { final List disconnectCallbacks = _callbacks[_kOnBillingServiceDisconnected] ??= []; - disconnectCallbacks.add(onBillingServiceDisconnected); + _callbacks[_kOnBillingServiceDisconnected] + ?.add(onBillingServiceDisconnected); return BillingResultWrapper.fromJson((await channel .invokeMapMethod( 'BillingClient#startConnection(BillingClientStateListener)', @@ -412,6 +428,15 @@ class BillingClient { _callbacks[_kOnBillingServiceDisconnected]! .cast(); onDisconnected[handle](); + case kUserSelectedAlternativeBilling: + if (_callbacks[kUserSelectedAlternativeBilling]!.isNotEmpty) { + final UserSelectedAlternativeBillingListener listener = + _callbacks[kUserSelectedAlternativeBilling]!.first + as UserSelectedAlternativeBillingListener; + listener(UserChoiceDetailsWrapper.fromJson( + (call.arguments as Map) + .cast())); + } } } } @@ -443,7 +468,7 @@ enum BillingResponse { @JsonValue(-2) featureNotSupported, - /// The play Store service is not connected now - potentially transient state. + /// The Play Store service is not connected now - potentially transient state. @JsonValue(-1) serviceDisconnected, @@ -490,8 +515,8 @@ enum BillingResponse { /// Plugin concept to cover billing modes. /// -/// [playBillingOnly] (google play billing only). -/// [alternativeBillingOnly] (app provided billing with reporting to play). +/// [playBillingOnly] (google Play billing only). +/// [alternativeBillingOnly] (app provided billing with reporting to Play). @JsonEnum(alwaysCreate: true) enum BillingChoiceMode { // WARNING: Changes to this class need to be reflected in our generated code. @@ -500,13 +525,17 @@ enum BillingChoiceMode { // Values must match what is used in // in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java - /// Billing through google play. Default state. + /// Billing through google Play. Default state. @JsonValue(0) playBillingOnly, /// Billing through app provided flow. @JsonValue(1) alternativeBillingOnly, + + /// Users can choose Play billing or alternative billing. + @JsonValue(2) + userChoiceBilling, } /// Serializer for [BillingChoiceMode]. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart index eb7b41afce14..0768c35deec8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart @@ -25,6 +25,7 @@ const _$BillingResponseEnumMap = { const _$BillingChoiceModeEnumMap = { BillingChoiceMode.playBillingOnly: 0, BillingChoiceMode.alternativeBillingOnly: 1, + BillingChoiceMode.userChoiceBilling: 2, }; const _$ProductTypeEnumMap = { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart new file mode 100644 index 000000000000..abdc31a178b0 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../../billing_client_wrappers.dart'; + +// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the +// below generated file. Run `flutter packages pub run build_runner watch` to +// rebuild and watch for further changes. +part 'user_choice_details_wrapper.g.dart'; + +/// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) +// See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes +// for explination for why this uses explicitToJson. +@JsonSerializable(createToJson: true, explicitToJson: true) +@immutable +class UserChoiceDetailsWrapper { + /// Creates a purchase wrapper with the given purchase details. + @visibleForTesting + const UserChoiceDetailsWrapper({ + required this.originalExternalTransactionId, + required this.externalTransactionToken, + required this.products, + }); + + /// Factory for creating a [UserChoiceDetailsWrapper] from a [Map] with + /// the user choice details. + factory UserChoiceDetailsWrapper.fromJson(Map map) => + _$UserChoiceDetailsWrapperFromJson(map); + + /// Creates a JSON representation of this product. + Map toJson() => _$UserChoiceDetailsWrapperToJson(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is UserChoiceDetailsWrapper && + other.originalExternalTransactionId == originalExternalTransactionId && + other.externalTransactionToken == externalTransactionToken && + listEquals(other.products, products); + } + + @override + int get hashCode => Object.hash( + originalExternalTransactionId, + externalTransactionToken, + products.hashCode, + ); + + /// Returns the external transaction Id of the originating subscription, if + /// the purchase is a subscription upgrade/downgrade. + @JsonKey(defaultValue: '') + final String originalExternalTransactionId; + + /// Returns a token that represents the user's prospective purchase via + /// user choice alternative billing. + @JsonKey(defaultValue: '') + final String externalTransactionToken; + + /// Returns a list of [UserChoiceDetailsProductWrapper] to be purchased in + /// the user choice alternative billing flow. + @JsonKey(defaultValue: []) + final List products; +} + +/// Data structure representing a UserChoiceDetails product. +/// +/// This wraps [`com.android.billingclient.api.UserChoiceDetails.Product`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails.Product) +// +// See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes +// for explination for why this uses explicitToJson. +@JsonSerializable(createToJson: true, explicitToJson: true) +@ProductTypeConverter() +@immutable +class UserChoiceDetailsProductWrapper { + /// Creates a [UserChoiceDetailsProductWrapper] with the given record details. + @visibleForTesting + const UserChoiceDetailsProductWrapper({ + required this.id, + required this.offerToken, + required this.productType, + }); + + /// Factory for creating a [UserChoiceDetailsProductWrapper] from a [Map] with the record details. + factory UserChoiceDetailsProductWrapper.fromJson(Map map) => + _$UserChoiceDetailsProductWrapperFromJson(map); + + /// Creates a JSON representation of this product. + Map toJson() => + _$UserChoiceDetailsProductWrapperToJson(this); + + /// Returns the id of the product being purchased. + @JsonKey(defaultValue: '') + final String id; + + /// Returns the offer token that was passed in launchBillingFlow to purchase the product. + @JsonKey(defaultValue: '') + final String offerToken; + + /// Returns the [ProductType] of the product being purchased. + final ProductType productType; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is UserChoiceDetailsProductWrapper && + other.id == id && + other.offerToken == offerToken && + other.productType == productType; + } + + @override + int get hashCode => Object.hash( + id, + offerToken, + productType, + ); +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart new file mode 100644 index 000000000000..669d263588d6 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_choice_details_wrapper.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserChoiceDetailsWrapper _$UserChoiceDetailsWrapperFromJson(Map json) => + UserChoiceDetailsWrapper( + originalExternalTransactionId: + json['originalExternalTransactionId'] as String? ?? '', + externalTransactionToken: + json['externalTransactionToken'] as String? ?? '', + products: (json['products'] as List?) + ?.map((e) => UserChoiceDetailsProductWrapper.fromJson( + Map.from(e as Map))) + .toList() ?? + [], + ); + +Map _$UserChoiceDetailsWrapperToJson( + UserChoiceDetailsWrapper instance) => + { + 'originalExternalTransactionId': instance.originalExternalTransactionId, + 'externalTransactionToken': instance.externalTransactionToken, + 'products': instance.products.map((e) => e.toJson()).toList(), + }; + +UserChoiceDetailsProductWrapper _$UserChoiceDetailsProductWrapperFromJson( + Map json) => + UserChoiceDetailsProductWrapper( + id: json['id'] as String? ?? '', + offerToken: json['offerToken'] as String? ?? '', + productType: + const ProductTypeConverter().fromJson(json['productType'] as String?), + ); + +Map _$UserChoiceDetailsProductWrapperToJson( + UserChoiceDetailsProductWrapper instance) => + { + 'id': instance.id, + 'offerToken': instance.offerToken, + 'productType': const ProductTypeConverter().toJson(instance.productType), + }; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index 732840802d28..e8c6f07a0bc7 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -2,19 +2,34 @@ // 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/services.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; import 'billing_client_wrappers/billing_config_wrapper.dart'; +import 'types/translator.dart'; /// Contains InApp Purchase features that are only available on PlayStore. class InAppPurchaseAndroidPlatformAddition extends InAppPurchasePlatformAddition { /// Creates a [InAppPurchaseAndroidPlatformAddition] which uses the supplied /// `BillingClientManager` to provide Android specific features. - InAppPurchaseAndroidPlatformAddition(this._billingClientManager); + InAppPurchaseAndroidPlatformAddition(this._billingClientManager) { + _billingClientManager.userChoiceDetailsStream + .map(Translator.convertToUserChoiceDetails) + .listen(_userChoiceDetailsStreamController.add); + } + + final StreamController + _userChoiceDetailsStreamController = + StreamController.broadcast(); + + /// [GooglePlayUserChoiceDetails] emits each time user selects alternative billing. + late final Stream userChoiceDetailsStream = + _userChoiceDetailsStreamController.stream; /// Whether pending purchase is enabled. /// diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart new file mode 100644 index 000000000000..97553b345759 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart @@ -0,0 +1,101 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +/// Data structure representing a UserChoiceDetails. +/// +/// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) +@immutable +class GooglePlayUserChoiceDetails { + /// Creates a new Google Play specific user choice billing details object with + /// the provided details. + const GooglePlayUserChoiceDetails({ + required this.originalExternalTransactionId, + required this.externalTransactionToken, + required this.products, + }); + + /// Returns the external transaction Id of the originating subscription, if + /// the purchase is a subscription upgrade/downgrade. + final String originalExternalTransactionId; + + /// Returns a token that represents the user's prospective purchase via + /// user choice alternative billing. + final String externalTransactionToken; + + /// Returns a list of [GooglePlayUserChoiceDetailsProduct] to be purchased in + /// the user choice alternative billing flow. + final List products; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is GooglePlayUserChoiceDetails && + other.originalExternalTransactionId == originalExternalTransactionId && + other.externalTransactionToken == externalTransactionToken && + listEquals(other.products, products); + } + + @override + int get hashCode => Object.hash( + originalExternalTransactionId, + externalTransactionToken, + products.hashCode, + ); +} + +/// Data structure representing a UserChoiceDetails product. +/// +/// This wraps [`com.android.billingclient.api.UserChoiceDetails.Product`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails.Product) +@immutable +class GooglePlayUserChoiceDetailsProduct { + /// Creates UserChoiceDetailsProduct. + const GooglePlayUserChoiceDetailsProduct( + {required this.id, required this.offerToken, required this.productType}); + + /// Returns the id of the product being purchased. + final String id; + + /// Returns the offer token that was passed in launchBillingFlow to purchase the product. + final String offerToken; + + /// Returns the [GooglePlayProductType] of the product being purchased. + final GooglePlayProductType productType; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is GooglePlayUserChoiceDetailsProduct && + other.id == id && + other.offerToken == offerToken && + other.productType == productType; + } + + @override + int get hashCode => Object.hash( + id, + offerToken, + productType, + ); +} + +/// This wraps [`com.android.billingclient.api.BillingClient.ProductType`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.ProductType) +enum GooglePlayProductType { + /// A Product type for Android apps in-app products. + inapp, + + /// A Product type for Android apps subscriptions. + subs +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart new file mode 100644 index 000000000000..f6461afc2c27 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import '../../billing_client_wrappers.dart'; +import 'google_play_user_choice_details.dart'; + +/// Class used to convert cross process object into api expose objects. +class Translator { + Translator._(); + + /// Converts from [UserChoiceDetailsWrapper] to [GooglePlayUserChoiceDetails]. + static GooglePlayUserChoiceDetails convertToUserChoiceDetails( + UserChoiceDetailsWrapper detailsWrapper) { + return GooglePlayUserChoiceDetails( + originalExternalTransactionId: + detailsWrapper.originalExternalTransactionId, + externalTransactionToken: detailsWrapper.externalTransactionToken, + products: detailsWrapper.products + .map((UserChoiceDetailsProductWrapper e) => + convertToUserChoiceDetailsProduct(e)) + .toList()); + } + + /// Converts from [UserChoiceDetailsProductWrapper] to [GooglePlayUserChoiceDetailsProduct]. + @visibleForTesting + static GooglePlayUserChoiceDetailsProduct convertToUserChoiceDetailsProduct( + UserChoiceDetailsProductWrapper productWrapper) { + return GooglePlayUserChoiceDetailsProduct( + id: productWrapper.id, + offerToken: productWrapper.offerToken, + productType: convertToPlayProductType(productWrapper.productType)); + } + + /// Coverts from [ProductType] to [GooglePlayProductType]. + @visibleForTesting + static GooglePlayProductType convertToPlayProductType(ProductType type) { + switch (type) { + case ProductType.inapp: + return GooglePlayProductType.inapp; + case ProductType.subs: + return GooglePlayProductType.subs; + } + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart index 0a43425f6e94..5116ac54e061 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart @@ -6,4 +6,5 @@ export 'change_subscription_param.dart'; export 'google_play_product_details.dart'; export 'google_play_purchase_details.dart'; export 'google_play_purchase_param.dart'; +export 'google_play_user_choice_details.dart'; export 'query_purchase_details_response.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 7ce2e0080ce2..5f57552f2336 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.1 +version: 0.3.2 environment: sdk: ^3.1.0 diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index b53bb5c96c3f..81874d75d8f9 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -138,5 +138,32 @@ void main() { expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(1)); }); + + test( + 'Emits UserChoiceDetailsWrapper when onUserChoiceAlternativeBilling is called', + () async { + connectedCompleter.complete(); + // Ensures all asynchronous connected code finishes. + await manager.runWithClientNonRetryable((_) async {}); + + const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( + originalExternalTransactionId: 'TransactionId', + externalTransactionToken: 'TransactionToken', + products: [ + UserChoiceDetailsProductWrapper( + id: 'id1', + offerToken: 'offerToken1', + productType: ProductType.inapp), + UserChoiceDetailsProductWrapper( + id: 'id2', + offerToken: 'offerToken2', + productType: ProductType.inapp), + ], + ); + final Future detailsFuture = + manager.userChoiceDetailsStream.first; + manager.onUserChoiceAlternativeBilling(expected); + expect(await detailsFuture, expected); + }); }); } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart index 3ffcd8d5d08e..92f14e2958ac 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart @@ -2,6 +2,8 @@ // 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/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; @@ -37,7 +39,8 @@ void main() { .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler)); setUp(() { - billingClient = BillingClient((PurchasesResultWrapper _) {}); + billingClient = BillingClient( + (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}); stubPlatform.reset(); }); @@ -114,7 +117,7 @@ void main() { })); }); - test('passes billingChoiceMode when set', () async { + test('passes billingChoiceMode alternativeBillingOnly when set', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; stubPlatform.addResponse( @@ -136,6 +139,91 @@ void main() { })); }); + test('passes billingChoiceMode userChoiceBilling when set', () async { + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.ok; + stubPlatform.addResponse( + name: methodName, + value: { + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'debugMessage': debugMessage, + }, + ); + final Completer completer = + Completer(); + + billingClient = BillingClient((PurchasesResultWrapper _) {}, + (UserChoiceDetailsWrapper details) => completer.complete(details)); + stubPlatform.reset(); + await billingClient.startConnection( + onBillingServiceDisconnected: () {}, + billingChoiceMode: BillingChoiceMode.userChoiceBilling); + final MethodCall call = stubPlatform.previousCallMatching(methodName); + expect( + call.arguments, + equals({ + 'handle': 0, + 'billingChoiceMode': 2, + })); + const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( + originalExternalTransactionId: 'TransactionId', + externalTransactionToken: 'TransactionToken', + products: [ + UserChoiceDetailsProductWrapper( + id: 'id1', + offerToken: 'offerToken1', + productType: ProductType.inapp), + UserChoiceDetailsProductWrapper( + id: 'id2', + offerToken: 'offerToken2', + productType: ProductType.inapp), + ], + ); + await billingClient.callHandler( + MethodCall(kUserSelectedAlternativeBilling, expected.toJson())); + expect(completer.isCompleted, isTrue); + expect(await completer.future, expected); + }); + + test('UserChoiceDetailsWrapper searilization check', () async { + // Test ensures that changes to UserChoiceDetailsWrapper#toJson are + // compatible with code in Translator.java. + const String transactionIdKey = 'originalExternalTransactionId'; + const String transactionTokenKey = 'externalTransactionToken'; + const String productsKey = 'products'; + const String productIdKey = 'id'; + const String productOfferTokenKey = 'offerToken'; + const String productTypeKey = 'productType'; + + const UserChoiceDetailsProductWrapper expectedProduct1 = + UserChoiceDetailsProductWrapper( + id: 'id1', + offerToken: 'offerToken1', + productType: ProductType.inapp); + const UserChoiceDetailsProductWrapper expectedProduct2 = + UserChoiceDetailsProductWrapper( + id: 'id2', + offerToken: 'offerToken2', + productType: ProductType.inapp); + const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( + originalExternalTransactionId: 'TransactionId', + externalTransactionToken: 'TransactionToken', + products: [ + expectedProduct1, + expectedProduct2, + ], + ); + final Map detailsJson = expected.toJson(); + expect(detailsJson.keys, contains(transactionIdKey)); + expect(detailsJson.keys, contains(transactionTokenKey)); + expect(detailsJson.keys, contains(productsKey)); + + final Map productJson = expectedProduct1.toJson(); + expect(productJson, contains(productIdKey)); + expect(productJson, contains(productOfferTokenKey)); + expect(productJson, contains(productTypeKey)); + }); + test('handles method channel returning null', () async { stubPlatform.addResponse( name: methodName, diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index 718bd3cdde1c..bf6612542bd5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -9,6 +9,7 @@ import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; import 'package:in_app_purchase_android/src/channel.dart'; +import 'package:in_app_purchase_android/src/types/translator.dart'; import 'billing_client_wrappers/billing_client_wrapper_test.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; @@ -281,4 +282,28 @@ void main() { expect(arguments['feature'], equals('subscriptions')); }); }); + + group('userChoiceDetails', () { + test('called', () async { + final Future futureDetails = + iapAndroidPlatformAddition.userChoiceDetailsStream.first; + const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( + originalExternalTransactionId: 'TransactionId', + externalTransactionToken: 'TransactionToken', + products: [ + UserChoiceDetailsProductWrapper( + id: 'id1', + offerToken: 'offerToken1', + productType: ProductType.inapp), + UserChoiceDetailsProductWrapper( + id: 'id2', + offerToken: 'offerToken2', + productType: ProductType.inapp), + ], + ); + manager.onUserChoiceAlternativeBilling(expected); + expect( + await futureDetails, Translator.convertToUserChoiceDetails(expected)); + }); + }); } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart new file mode 100644 index 000000000000..7e19c4b41572 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_android/billing_client_wrappers.dart'; +import 'package:in_app_purchase_android/src/types/google_play_user_choice_details.dart'; +import 'package:in_app_purchase_android/src/types/translator.dart'; +import 'package:test/test.dart'; + +void main() { + group('Translator ', () { + test('convertToPlayProductType', () { + expect(Translator.convertToPlayProductType(ProductType.inapp), + GooglePlayProductType.inapp); + expect(Translator.convertToPlayProductType(ProductType.subs), + GooglePlayProductType.subs); + expect(GooglePlayProductType.values.length, ProductType.values.length); + }); + + test('convertToUserChoiceDetailsProduct', () { + const GooglePlayUserChoiceDetailsProduct expected = + GooglePlayUserChoiceDetailsProduct( + id: 'id', + offerToken: 'offerToken', + productType: GooglePlayProductType.inapp); + expect( + Translator.convertToUserChoiceDetailsProduct( + UserChoiceDetailsProductWrapper( + id: expected.id, + offerToken: expected.offerToken, + productType: ProductType.inapp)), + expected); + }); + test('convertToUserChoiceDetailsProduct', () { + const GooglePlayUserChoiceDetailsProduct expectedProduct1 = + GooglePlayUserChoiceDetailsProduct( + id: 'id1', + offerToken: 'offerToken1', + productType: GooglePlayProductType.inapp); + const GooglePlayUserChoiceDetailsProduct expectedProduct2 = + GooglePlayUserChoiceDetailsProduct( + id: 'id2', + offerToken: 'offerToken2', + productType: GooglePlayProductType.subs); + const GooglePlayUserChoiceDetails expected = GooglePlayUserChoiceDetails( + originalExternalTransactionId: 'originalExternalTransactionId', + externalTransactionToken: 'externalTransactionToken', + products: [ + expectedProduct1, + expectedProduct2 + ]); + + expect( + Translator.convertToUserChoiceDetails(UserChoiceDetailsWrapper( + originalExternalTransactionId: + expected.originalExternalTransactionId, + externalTransactionToken: expected.externalTransactionToken, + products: [ + UserChoiceDetailsProductWrapper( + id: expectedProduct1.id, + offerToken: expectedProduct1.offerToken, + productType: ProductType.inapp), + UserChoiceDetailsProductWrapper( + id: expectedProduct2.id, + offerToken: expectedProduct2.offerToken, + productType: ProductType.subs), + ])), + expected); + }); + }); +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index c19e4a826dd8..fce36eb13a6b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.3.13+1 + +* Handle translation of errors nested in dictionaries. + +## 0.3.13 + +* Added new native tests for more complete test coverage. + +## 0.3.12+1 + +* Fixes type of error code returned from native code in SKReceiptManager.retrieveReceiptData. + +## 0.3.12 + +* Converts `refreshReceipt()`, `startObservingPaymentQueue()`, `stopObservingPaymentQueue()`, +`registerPaymentQueueDelegate()`, `removePaymentQueueDelegate()`, `showPriceConsentIfNeeded()` to pigeon. + ## 0.3.11 * Fixes SKError.userInfo not being nullable. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m index b8c6a269b8ce..b9509eb4bee9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m @@ -166,12 +166,11 @@ + (NSDictionary *)getMapFromNSError:(NSError *)error { return nil; } - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - for (NSErrorUserInfoKey key in error.userInfo) { - id value = error.userInfo[key]; - userInfo[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value]; - } - return @{@"code" : @(error.code), @"domain" : error.domain ?: @"", @"userInfo" : userInfo}; + return @{ + @"code" : @(error.code), + @"domain" : error.domain ?: @"", + @"userInfo" : [FIAObjectTranslator encodeNSErrorUserInfo:error.userInfo] + }; } + (id)encodeNSErrorUserInfo:(id)value { @@ -189,6 +188,12 @@ + (id)encodeNSErrorUserInfo:(id)value { [errors addObject:[FIAObjectTranslator encodeNSErrorUserInfo:error]]; } return errors; + } else if ([value isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *errors = [[NSMutableDictionary alloc] init]; + for (id key in value) { + errors[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value[key]]; + } + return errors; } else { return [NSString stringWithFormat: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index 320e6072d046..22a3973b7fca 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -29,9 +29,10 @@ - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError { if (!receipt || receiptError) { if (flutterError) { NSDictionary *errorMap = [FIAObjectTranslator getMapFromNSError:receiptError]; - *flutterError = [FlutterError errorWithCode:errorMap[@"code"] - message:errorMap[@"domain"] - details:errorMap[@"userInfo"]]; + *flutterError = + [FlutterError errorWithCode:[NSString stringWithFormat:@"%@", errorMap[@"code"]] + message:errorMap[@"domain"] + details:errorMap[@"userInfo"]]; } return nil; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h new file mode 100644 index 000000000000..ca090b6b9928 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import "FIAPRequestHandler.h" +#import "InAppPurchasePlugin.h" + +@interface InAppPurchasePlugin () + +// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after +// the request is finished. +@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; + +// Callback channel to dart used for when a function from the transaction observer is triggered. +@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; + +// Callback channel to dart used for when a function from the transaction observer is triggered. +@property(copy, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); + +// Convenience initializer with dependancy injection +- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager + handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory; + +// Transaction observer methods +- (void)handleTransactionsUpdated:(NSArray *)transactions; +- (void)handleTransactionsRemoved:(NSArray *)transactions; +- (void)handleTransactionRestoreFailed:(NSError *)error; +- (void)restoreCompletedTransactionsFinished; +- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 82366537404b..2e2abaaed35b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -9,20 +9,14 @@ #import "FIAPReceiptManager.h" #import "FIAPRequestHandler.h" #import "FIAPaymentQueueHandler.h" +#import "InAppPurchasePlugin+TestOnly.h" @interface InAppPurchasePlugin () -// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after -// the request is finished. -@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; - // After querying the product, the available products will be saved in the map to be used // for purchase. @property(strong, nonatomic, readonly) NSMutableDictionary *productsCache; -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel; - // Callback channel to dart used for when a function from the payment queue delegate is triggered. @property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel; @property(strong, nonatomic, readonly) NSObject *registrar; @@ -52,6 +46,16 @@ - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { _receiptManager = receiptManager; _requestHandlers = [NSMutableSet new]; _productsCache = [NSMutableDictionary new]; + _handlerFactory = ^FIAPRequestHandler *(SKRequest *request) { + return [[FIAPRequestHandler alloc] initWithRequest:request]; + }; + return self; +} + +- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager + handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory { + self = [self initWithReceiptManager:receiptManager]; + _handlerFactory = [handlerFactory copy]; return self; } @@ -87,30 +91,6 @@ - (instancetype)initWithRegistrar:(NSObject *)registrar return self; } -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) { - [self retrieveReceiptData:call result:result]; - } else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) { - [self refreshReceipt:call result:result]; - } else if ([@"-[SKPaymentQueue startObservingTransactionQueue]" isEqualToString:call.method]) { - [self startObservingPaymentQueue:result]; - } else if ([@"-[SKPaymentQueue stopObservingTransactionQueue]" isEqualToString:call.method]) { - [self stopObservingPaymentQueue:result]; -#if TARGET_OS_IOS - } else if ([@"-[SKPaymentQueue registerDelegate]" isEqualToString:call.method]) { - [self registerPaymentQueueDelegate:result]; -#endif - } else if ([@"-[SKPaymentQueue removeDelegate]" isEqualToString:call.method]) { - [self removePaymentQueueDelegate:result]; -#if TARGET_OS_IOS - } else if ([@"-[SKPaymentQueue showPriceConsentIfNeeded]" isEqualToString:call.method]) { - [self showPriceConsentIfNeeded:result]; -#endif - } else { - result(FlutterMethodNotImplemented); - } -} - - (nullable NSNumber *)canMakePaymentsWithError: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { return @([SKPaymentQueue canMakePayments]); @@ -141,7 +121,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray *)productIden FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; + FIAPRequestHandler *handler = self.handlerFactory(request); [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; @@ -270,62 +250,62 @@ - (void)presentCodeRedemptionSheetWithError: #endif } -- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result { - FlutterError *error = nil; - NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&error]; - if (error) { - result(error); - return; +- (nullable NSString *)retrieveReceiptDataWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { + FlutterError *flutterError; + NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&flutterError]; + if (flutterError) { + *error = flutterError; + return nil; } - result(receiptData); + return receiptData; } -- (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result { - NSDictionary *arguments = call.arguments; +- (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties + completion:(nonnull void (^)(FlutterError *_Nullable))completion { SKReceiptRefreshRequest *request; - if (arguments) { - if (![arguments isKindOfClass:[NSDictionary class]]) { - result([FlutterError errorWithCode:@"storekit_invalid_argument" - message:@"Argument type of startRequest is not array" - details:call.arguments]); - return; - } + if (receiptProperties) { + // if recieptProperties is not null, this call is for testing. NSMutableDictionary *properties = [NSMutableDictionary new]; - properties[SKReceiptPropertyIsExpired] = arguments[@"isExpired"]; - properties[SKReceiptPropertyIsRevoked] = arguments[@"isRevoked"]; - properties[SKReceiptPropertyIsVolumePurchase] = arguments[@"isVolumePurchase"]; + properties[SKReceiptPropertyIsExpired] = receiptProperties[@"isExpired"]; + properties[SKReceiptPropertyIsRevoked] = receiptProperties[@"isRevoked"]; + properties[SKReceiptPropertyIsVolumePurchase] = receiptProperties[@"isVolumePurchase"]; request = [self getRefreshReceiptRequest:properties]; } else { request = [self getRefreshReceiptRequest:nil]; } - FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; + + FIAPRequestHandler *handler = self.handlerFactory(request); [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, NSError *_Nullable error) { + FlutterError *requestError; if (error) { - result([FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" - message:error.localizedDescription - details:error.description]); + requestError = [FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" + message:error.localizedDescription + details:error.description]; + completion(requestError); return; } - result(nil); + completion(nil); [weakSelf.requestHandlers removeObject:handler]; }]; } -- (void)startObservingPaymentQueue:(FlutterResult)result { +- (void)startObservingPaymentQueueWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { [_paymentQueueHandler startObservingPaymentQueue]; - result(nil); } -- (void)stopObservingPaymentQueue:(FlutterResult)result { +- (void)stopObservingPaymentQueueWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { [_paymentQueueHandler stopObservingPaymentQueue]; - result(nil); } +- (void)registerPaymentQueueDelegateWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS -- (void)registerPaymentQueueDelegate:(FlutterResult)result { if (@available(iOS 13.0, *)) { _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate" @@ -335,27 +315,25 @@ - (void)registerPaymentQueueDelegate:(FlutterResult)result { initWithMethodChannel:_paymentQueueDelegateCallbackChannel]; _paymentQueueHandler.delegate = _paymentQueueDelegate; } - result(nil); -} #endif +} -- (void)removePaymentQueueDelegate:(FlutterResult)result { +- (void)removePaymentQueueDelegateWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { if (@available(iOS 13.0, *)) { _paymentQueueHandler.delegate = nil; } _paymentQueueDelegate = nil; _paymentQueueDelegateCallbackChannel = nil; - result(nil); } +- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS -- (void)showPriceConsentIfNeeded:(FlutterResult)result { if (@available(iOS 13.4, *)) { [_paymentQueueHandler showPriceConsentIfNeeded]; } - result(nil); -} #endif +} - (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { id value = dictionary[key]; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h index 5d5b3a0c7799..bc315e781cd6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - // Autogenerated from Pigeon (v16.0.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -271,6 +270,14 @@ NSObject *InAppPurchaseAPIGetCodec(void); - (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName error:(FlutterError *_Nullable *_Nonnull)error; - (void)presentCodeRedemptionSheetWithError:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSString *)retrieveReceiptDataWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties + completion:(void (^)(FlutterError *_Nullable))completion; +- (void)startObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)stopObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)registerPaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)removePaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable *_Nonnull)error; @end extern void SetUpInAppPurchaseAPI(id binaryMessenger, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m index bbb5d6b67eca..9588c883b820 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - // Autogenerated from Pigeon (v16.0.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -752,4 +751,147 @@ void SetUpInAppPurchaseAPI(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(retrieveReceiptDataWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to @selector(retrieveReceiptDataWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSString *output = [api retrieveReceiptDataWithError:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(refreshReceiptReceiptProperties:completion:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(refreshReceiptReceiptProperties:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSDictionary *arg_receiptProperties = GetNullableObjectAtIndex(args, 0); + [api refreshReceiptReceiptProperties:arg_receiptProperties + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"startObservingPaymentQueue" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(startObservingPaymentQueueWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(startObservingPaymentQueueWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api startObservingPaymentQueueWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"stopObservingPaymentQueue" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(stopObservingPaymentQueueWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(stopObservingPaymentQueueWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api stopObservingPaymentQueueWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"registerPaymentQueueDelegate" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(registerPaymentQueueDelegateWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(registerPaymentQueueDelegateWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api registerPaymentQueueDelegateWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"removePaymentQueueDelegate" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(removePaymentQueueDelegateWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(removePaymentQueueDelegateWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api removePaymentQueueDelegateWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"showPriceConsentIfNeeded" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(showPriceConsentIfNeededWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(showPriceConsentIfNeededWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api showPriceConsentIfNeededWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 102a865d991a..477131feb699 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> #import #import "FIAPaymentQueueHandler.h" +#import "InAppPurchasePlugin+TestOnly.h" #import "Stubs.h" @import in_app_purchase_storekit; @@ -26,20 +27,6 @@ - (void)setUp { - (void)tearDown { } -- (void)testInvalidMethodCall { - XCTestExpectation *expectation = - [self expectationWithDescription:@"expect result to be not implemented"]; - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"invalid" arguments:NULL]; - __block id result; - [self.plugin handleMethodCall:call - result:^(id r) { - [expectation fulfill]; - result = r; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertEqual(result, FlutterMethodNotImplemented); -} - - (void)testCanMakePayments { FlutterError *error; NSNumber *result = [self.plugin canMakePaymentsWithError:&error]; @@ -122,6 +109,142 @@ - (void)testGetProductResponse { [self waitForExpectations:@[ expectation ] timeout:5]; } +- (void)testFinishTransactionSucceeds { + NSDictionary *args = @{ + @"transactionIdentifier" : @"567", + @"productIdentifier" : @"unique_identifier", + }; + + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = @[ paymentTransaction ]; + + FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); + OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); + + self.plugin.paymentQueueHandler = mockHandler; + + FlutterError *error; + [self.plugin finishTransactionFinishMap:args error:&error]; + + XCTAssertNil(error); +} + +- (void)testFinishTransactionSucceedsWithNilTransaction { + NSDictionary *args = @{ + @"transactionIdentifier" : [NSNull null], + @"productIdentifier" : @"unique_identifier", + }; + + NSDictionary *paymentMap = @{ + @"productIdentifier" : @"123", + @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + @"quantity" : @(2), + @"applicationUsername" : @"app user name", + @"simulatesAskToBuyInSandbox" : @(NO) + }; + + NSDictionary *transactionMap = @{ + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : paymentMap, + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + + FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); + OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]); + + self.plugin.paymentQueueHandler = mockHandler; + + FlutterError *error; + [self.plugin finishTransactionFinishMap:args error:&error]; + + XCTAssertNil(error); +} + +- (void)testGetProductResponseWithRequestError { + NSArray *argument = @[ @"123" ]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; + + NSError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + + OCMStub([mockHandler + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, + nil])]); + + [plugin + startProductRequestProductIdentifiers:argument + completion:^(SKProductsResponseMessage *_Nullable response, + FlutterError *_Nullable startProductRequestError) { + [expectation fulfill]; + XCTAssertNotNil(error); + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects( + startProductRequestError.code, + @"storekit_getproductrequest_platform_error"); + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; +} + +- (void)testGetProductResponseWithNoResponse { + NSArray *argument = @[ @"123" ]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; + + NSError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + + OCMStub([mockHandler + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], + [NSNull null], nil])]); + + [plugin + startProductRequestProductIdentifiers:argument + completion:^(SKProductsResponseMessage *_Nullable response, + FlutterError *_Nullable startProductRequestError) { + [expectation fulfill]; + XCTAssertNotNil(error); + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects(startProductRequestError.code, + @"storekit_platform_no_response"); + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; +} + - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { NSDictionary *argument = @{ @"productIdentifier" : @"123", @@ -146,6 +269,27 @@ - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { XCTAssertEqualObjects(argument, error.details); } +- (void)testAddPaymentShouldReturnFlutterErrorWhenInvalidProduct { + NSDictionary *argument = @{ + // stubbed function will return nil for an empty productIdentifier + @"productIdentifier" : @"", + @"quantity" : @(1), + @"simulatesAskToBuyInSandbox" : @YES, + }; + + FlutterError *error; + + [self.plugin addPaymentPaymentMap:argument error:&error]; + + XCTAssertEqualObjects(@"storekit_invalid_payment_object", error.code); + XCTAssertEqualObjects( + @"You have requested a payment for an invalid product. Either the " + @"`productIdentifier` of the payment is not valid or the product has not been " + @"fetched before adding the payment to the payment queue.", + error.message); + XCTAssertEqualObjects(argument, error.details); +} + - (void)testAddPaymentSuccessWithoutPaymentDiscount { NSDictionary *argument = @{ @"productIdentifier" : @"123", @@ -299,17 +443,8 @@ - (void)testRestoreTransactions { } - (void)testRetrieveReceiptDataSuccess { - XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" - arguments:nil]; - __block NSDictionary *result; - [self.plugin handleMethodCall:call - result:^(id r) { - result = r; - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; + FlutterError *error; + NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; XCTAssertNotNil(result); XCTAssert([result isKindOfClass:[NSString class]]); } @@ -317,72 +452,99 @@ - (void)testRetrieveReceiptDataSuccess { - (void)testRetrieveReceiptDataNil { NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); - XCTestExpectation *expectation = [self expectationWithDescription:@"nil receipt data retrieved"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" - arguments:nil]; - __block NSDictionary *result; - [self.plugin handleMethodCall:call - result:^(id r) { - result = r; - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; + FlutterError *error; + NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; XCTAssertNil(result); } - (void)testRetrieveReceiptDataError { - XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" - arguments:nil]; - __block NSDictionary *result; self.receiptManagerStub.returnError = YES; - [self.plugin handleMethodCall:call - result:^(id r) { - result = r; - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertNotNil(result); - XCTAssert([result isKindOfClass:[FlutterError class]]); - NSDictionary *details = ((FlutterError *)result).details; + + FlutterError *error; + NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; + + XCTAssertNil(result); + XCTAssertNotNil(error); + XCTAssert([error.code isKindOfClass:[NSString class]]); + NSDictionary *details = error.details; XCTAssertNotNil(details[@"error"]); NSNumber *errorCode = (NSNumber *)details[@"error"][@"code"]; XCTAssertEqual(errorCode, [NSNumber numberWithInteger:99]); } - (void)testRefreshReceiptRequest { - XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin refreshReceipt:result:]" - arguments:nil]; - __block BOOL result = NO; - [self.plugin handleMethodCall:call - result:^(id r) { - result = YES; - [expectation fulfill]; - }]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; + [self.plugin refreshReceiptReceiptProperties:nil + completion:^(FlutterError *_Nullable error) { + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertTrue(result); } -- (void)testPresentCodeRedemptionSheet { +- (void)testRefreshReceiptRequestWithParams { + NSDictionary *properties = @{ + @"isExpired" : @NO, + @"isRevoked" : @NO, + @"isVolumePurchase" : @NO, + }; + XCTestExpectation *expectation = - [self expectationWithDescription:@"expect successfully present Code Redemption Sheet"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" - arguments:nil]; - __block BOOL callbackInvoked = NO; - [self.plugin handleMethodCall:call - result:^(id r) { - callbackInvoked = YES; - [expectation fulfill]; - }]; + [self expectationWithDescription:@"completion handler successfully called"]; + [self.plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; +} + +- (void)testRefreshReceiptRequestWithError { + NSDictionary *properties = @{ + @"isExpired" : @NO, + @"isRevoked" : @NO, + @"isVolumePurchase" : @NO, + }; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; + + NSError *recieptError = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + + OCMStub([mockHandler + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], + recieptError, nil])]); + + [plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects( + error.code, @"storekit_refreshreceiptrequest_platform_error"); + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertTrue(callbackInvoked); } +/// presentCodeRedemptionSheetWithError:error is only available on iOS +#if TARGET_OS_IOS +- (void)testPresentCodeRedemptionSheet { + FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); + self.plugin.paymentQueueHandler = mockHandler; + + FlutterError *error; + [self.plugin presentCodeRedemptionSheetWithError:&error]; + + OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]); +} +#endif + - (void)testGetPendingTransactions { SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); NSDictionary *transactionMap = @{ @@ -420,48 +582,28 @@ - (void)testGetPendingTransactions { } - (void)testStartObservingPaymentQueue { - XCTestExpectation *expectation = - [self expectationWithDescription:@"Should return success result"]; - FlutterMethodCall *startCall = [FlutterMethodCall - methodCallWithMethodName:@"-[SKPaymentQueue startObservingTransactionQueue]" - arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; - [self.plugin handleMethodCall:startCall - result:^(id _Nullable result) { - XCTAssertNil(result); - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; + FlutterError *error; + [self.plugin startObservingPaymentQueueWithError:&error]; + OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); } - (void)testStopObservingPaymentQueue { - XCTestExpectation *expectation = - [self expectationWithDescription:@"Should return success result"]; - FlutterMethodCall *stopCall = - [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue stopObservingTransactionQueue]" - arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; - [self.plugin handleMethodCall:stopCall - result:^(id _Nullable result) { - XCTAssertNil(result); - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; + FlutterError *error; + [self.plugin stopObservingPaymentQueueWithError:&error]; + OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); } #if TARGET_OS_IOS - (void)testRegisterPaymentQueueDelegate { if (@available(iOS 13, *)) { - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue registerDelegate]" - arguments:nil]; - self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil @@ -475,9 +617,8 @@ - (void)testRegisterPaymentQueueDelegate { // Verify the delegate is nil before we register one. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); - [self.plugin handleMethodCall:call - result:^(id r){ - }]; + FlutterError *error; + [self.plugin registerPaymentQueueDelegateWithError:&error]; // Verify the delegate is not nil after we registered one. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); @@ -487,10 +628,6 @@ - (void)testRegisterPaymentQueueDelegate { - (void)testRemovePaymentQueueDelegate { if (@available(iOS 13, *)) { - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue removeDelegate]" - arguments:nil]; - self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil @@ -505,27 +642,131 @@ - (void)testRemovePaymentQueueDelegate { // Verify the delegate is not nil before removing it. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); - [self.plugin handleMethodCall:call - result:^(id r){ - }]; + FlutterError *error; + [self.plugin removePaymentQueueDelegateWithError:&error]; // Verify the delegate is nill after removing it. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); } } +- (void)testHandleTransactionsUpdated { + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSMutableArray *maps = [NSMutableArray new]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + + [plugin handleTransactionsUpdated:array]; + OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]); +} + +- (void)testHandleTransactionsRemoved { + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSMutableArray *maps = [NSMutableArray new]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + + [plugin handleTransactionsRemoved:array]; + OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]); +} + +- (void)testHandleTransactionRestoreFailed { + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + NSError *error; + [plugin handleTransactionRestoreFailed:error]; + OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" + arguments:[FIAObjectTranslator getMapFromNSError:error]]); +} + +- (void)testRestoreCompletedTransactionsFinished { + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + [plugin restoreCompletedTransactionsFinished]; + OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" + arguments:nil]); +} + +- (void)testShouldAddStorePayment { + NSDictionary *paymentMap = @{ + @"productIdentifier" : @"123", + @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + @"quantity" : @(2), + @"applicationUsername" : @"app user name", + @"simulatesAskToBuyInSandbox" : @(NO) + }; + + NSDictionary *productMap = @{ + @"price" : @"1", + @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], + @"productIdentifier" : @"123", + @"localizedTitle" : @"title", + @"localizedDescription" : @"des", + }; + + SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; + SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + NSDictionary *args = @{ + @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], + @"product" : [FIAObjectTranslator getMapFromSKProduct:product] + }; + + BOOL result = [plugin shouldAddStorePayment:payment product:product]; + XCTAssertEqual(result, NO); + OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]); +} + #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue showPriceConsentIfNeeded]" - arguments:nil]; - FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); self.plugin.paymentQueueHandler = mockQueueHandler; - [self.plugin handleMethodCall:call - result:^(id r){ - }]; + FlutterError *error; + [self.plugin showPriceConsentIfNeededWithError:&error]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h index d4e8df3eba72..2ef8e23181a7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h @@ -23,6 +23,7 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @end @interface SKProductRequestStub : SKProductsRequest +@property(assign, nonatomic) BOOL returnError; - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @@ -34,6 +35,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface InAppPurchasePluginStub : InAppPurchasePlugin @end +@interface SKRequestStub : SKRequest +@end + @interface SKPaymentQueueStub : SKPaymentQueue @property(assign, nonatomic) SKPaymentTransactionState testState; @property(strong, nonatomic, nullable) id observer; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index 38081bb18f9c..b4dba710f026 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -111,8 +111,13 @@ - (void)start { for (NSString *identifier in self.identifers) { [productArray addObject:@{@"productIdentifier" : identifier}]; } - SKProductsResponseStub *response = - [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; + SKProductsResponseStub *response; + if (self.returnError) { + response = nil; + } else { + response = [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; + } + if (self.error) { [self.delegate request:self didFailWithError:self.error]; } else { @@ -140,7 +145,6 @@ - (instancetype)initWithMap:(NSDictionary *)map { @end @interface InAppPurchasePluginStub () - @end @implementation InAppPurchasePluginStub @@ -150,6 +154,9 @@ - (SKProductRequestStub *)getProductRequestWithIdentifiers:(NSSet *)identifiers } - (SKProduct *)getProduct:(NSString *)productID { + if ([productID isEqualToString:@""]) { + return nil; + } return [[SKProductStub alloc] initWithProductID:productID]; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m index 0060051dad6a..7ffe4c6ac7ff 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m @@ -191,6 +191,26 @@ - (void)testErrorWithMultipleUnderlyingErrors { XCTAssertEqualObjects(expectedMap, map); } +- (void)testErrorWithNestedUnderlyingError { + NSError *underlyingError = [NSError errorWithDomain:SKErrorDomain code:2 userInfo:nil]; + NSError *mainError = + [NSError errorWithDomain:SKErrorDomain + code:3 + userInfo:@{@"nesting" : @{@"underlyingError" : underlyingError}}]; + NSDictionary *expectedMap = @{ + @"domain" : SKErrorDomain, + @"code" : @3, + @"userInfo" : @{ + @"nesting" : @{ + @"underlyingError" : @{@"domain" : SKErrorDomain, @"code" : @2, @"userInfo" : @{}}, + + } + } + }; + NSDictionary *map = [FIAObjectTranslator getMapFromNSError:mainError]; + XCTAssertEqualObjects(expectedMap, map); +} + - (void)testErrorWithUnsupportedUserInfo { NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart index 27a38c9e57a9..7ce35bded2b2 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart @@ -1,3 +1,6 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. // Autogenerated from Pigeon (v16.0.4), 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, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -788,4 +791,173 @@ class InAppPurchaseAPI { return; } } + + Future retrieveReceiptData() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } + + Future refreshReceipt( + {Map? receiptProperties}) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([receiptProperties]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future startObservingPaymentQueue() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future stopObservingPaymentQueue() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future registerPaymentQueueDelegate() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future removePaymentQueueDelegate() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future showPriceConsentIfNeeded() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index dd5571a7af0a..8f1af1c64049 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -86,16 +86,16 @@ class SKPaymentQueueWrapper { /// /// Call this method when the first listener is subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. - Future startObservingTransactionQueue() => channel - .invokeMethod('-[SKPaymentQueue startObservingTransactionQueue]'); + Future startObservingTransactionQueue() => + _hostApi.startObservingPaymentQueue(); /// Instructs the iOS implementation to remove the transaction observer and /// stop listening to it. /// /// Call this when there are no longer any listeners subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. - Future stopObservingTransactionQueue() => channel - .invokeMethod('-[SKPaymentQueue stopObservingTransactionQueue]'); + Future stopObservingTransactionQueue() => + _hostApi.stopObservingPaymentQueue(); /// Sets an implementation of the [SKPaymentQueueDelegateWrapper]. /// @@ -109,10 +109,10 @@ class SKPaymentQueueWrapper { /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)). Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) async { if (delegate == null) { - await channel.invokeMethod('-[SKPaymentQueue removeDelegate]'); + await _hostApi.removePaymentQueueDelegate(); paymentQueueDelegateChannel.setMethodCallHandler(null); } else { - await channel.invokeMethod('-[SKPaymentQueue registerDelegate]'); + await _hostApi.registerPaymentQueueDelegate(); paymentQueueDelegateChannel .setMethodCallHandler(handlePaymentQueueDelegateCallbacks); } @@ -207,8 +207,7 @@ class SKPaymentQueueWrapper { /// /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc). Future showPriceConsentIfNeeded() async { - await channel - .invokeMethod('-[SKPaymentQueue showPriceConsentIfNeeded]'); + await _hostApi.showPriceConsentIfNeeded(); } /// Triage a method channel call from the platform and triggers the correct observer method. @@ -354,7 +353,7 @@ class SKError { /// /// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). @JsonKey(defaultValue: {}) - final Map userInfo; + final Map? userInfo; @override bool operator ==(Object other) { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart index b31a3d59c172..f61105aa2136 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart @@ -4,7 +4,9 @@ import 'dart:async'; -import '../channel.dart'; +import '../messages.g.dart'; + +InAppPurchaseAPI _hostApi = InAppPurchaseAPI(); // ignore: avoid_classes_with_only_static_members /// This class contains static methods to manage StoreKit receipts. @@ -17,8 +19,6 @@ class SKReceiptManager { /// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1). /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt. static Future retrieveReceiptData() async { - return (await channel.invokeMethod( - '-[InAppPurchasePlugin retrieveReceiptData:result:]')) ?? - ''; + return (await _hostApi.retrieveReceiptData()) ?? ''; } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart index 2dc2ad7baef2..5a16f261cdd7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import '../channel.dart'; import '../messages.g.dart'; import 'sk_product_wrapper.dart'; @@ -54,9 +53,6 @@ class SKRequestMaker { /// * isVolumePurchase: whether the receipt is a Volume Purchase Plan receipt. Future startRefreshReceiptRequest( {Map? receiptProperties}) { - return channel.invokeMethod( - '-[InAppPurchasePlugin refreshReceipt:result:]', - receiptProperties, - ); + return _hostApi.refreshReceipt(receiptProperties: receiptProperties); } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt index e69de29bb2d1..fb682b1ab965 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart index c5d352650408..fe1042c8037e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart @@ -243,4 +243,19 @@ abstract class InAppPurchaseAPI { void restoreTransactions(String? applicationUserName); void presentCodeRedemptionSheet(); + + String? retrieveReceiptData(); + + @async + void refreshReceipt({Map? receiptProperties}); + + void startObservingPaymentQueue(); + + void stopObservingPaymentQueue(); + + void registerPaymentQueueDelegate(); + + void removePaymentQueueDelegate(); + + void showPriceConsentIfNeeded(); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index 920873e65273..08ba026a014a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.11 +version: 0.3.13+1 environment: sdk: ^3.2.3 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 48db0847bf30..8fff5754eb77 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -5,7 +5,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; -import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; @@ -13,11 +12,6 @@ import '../store_kit_wrappers/sk_test_stub_objects.dart'; import '../test_api.g.dart'; class FakeStoreKitPlatform implements TestInAppPurchaseApi { - FakeStoreKitPlatform() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, onMethodCall); - } - // pre-configured store information String? receiptData; late Set validProductIDs; @@ -32,6 +26,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { SKError? testRestoredError; bool queueIsActive = false; Map discountReceived = {}; + bool isPaymentQueueDelegateRegistered = false; void reset() { transactionList = []; @@ -57,6 +52,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { testRestoredError = null; queueIsActive = false; discountReceived = {}; + isPaymentQueueDelegateRegistered = false; } SKPaymentTransactionWrapper createPendingTransaction(String id, @@ -120,25 +116,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { transactionIdentifier: transactionId); } - Future onMethodCall(MethodCall call) { - switch (call.method) { - case '-[InAppPurchasePlugin retrieveReceiptData:result:]': - if (receiptData != null) { - return Future.value(receiptData!); - } else { - throw PlatformException(code: 'no_receipt_data'); - } - case '-[InAppPurchasePlugin refreshReceipt:result:]': - receiptData = 'refreshed receipt data'; - return Future.sync(() {}); - case '-[SKPaymentQueue startObservingTransactionQueue]': - queueIsActive = true; - case '-[SKPaymentQueue stopObservingTransactionQueue]': - queueIsActive = false; - } - return Future.sync(() {}); - } - @override bool canMakePayments() { return true; @@ -246,4 +223,42 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { return Future.value( SkProductResponseWrapper.convertToPigeon(response)); } + + @override + Future refreshReceipt({Map? receiptProperties}) { + receiptData = 'refreshed receipt data'; + return Future.sync(() {}); + } + + @override + void registerPaymentQueueDelegate() { + isPaymentQueueDelegateRegistered = true; + } + + @override + void removePaymentQueueDelegate() { + isPaymentQueueDelegateRegistered = false; + } + + @override + String retrieveReceiptData() { + if (receiptData != null) { + return receiptData!; + } else { + throw PlatformException(code: 'no_receipt_data'); + } + } + + @override + void showPriceConsentIfNeeded() {} + + @override + void startObservingPaymentQueue() { + queueIsActive = true; + } + + @override + void stopObservingPaymentQueue() { + queueIsActive = false; + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart index 99fd49d03a32..40068db75a71 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; @@ -17,9 +16,6 @@ void main() { setUpAll(() { TestInAppPurchaseApi.setup(fakeStoreKitPlatform); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); group('present code redemption sheet', () { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 278a69184468..e268c23bdaa4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -23,9 +23,6 @@ void main() { setUpAll(() { TestInAppPurchaseApi.setup(fakeStoreKitPlatform); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index 704be83c8e25..82775f6b2e21 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -4,7 +4,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import '../test_api.g.dart'; @@ -17,9 +16,6 @@ void main() { setUpAll(() { TestInAppPurchaseApi.setup(fakeStoreKitPlatform); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() {}); @@ -76,10 +72,10 @@ void main() { }); test('refreshed receipt', () async { - final int receiptCountBefore = fakeStoreKitPlatform.refreshReceipt; + final int receiptCountBefore = fakeStoreKitPlatform.refreshReceiptCount; await SKRequestMaker().startRefreshReceiptRequest( receiptProperties: {'isExpired': true}); - expect(fakeStoreKitPlatform.refreshReceipt, receiptCountBefore + 1); + expect(fakeStoreKitPlatform.refreshReceiptCount, receiptCountBefore + 1); expect(fakeStoreKitPlatform.refreshReceiptParam, {'isExpired': true}); }); @@ -175,9 +171,9 @@ void main() { }); test('showPriceConsentIfNeeded should call methodChannel', () async { - expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, false); + expect(fakeStoreKitPlatform.showPriceConsent, false); await SKPaymentQueueWrapper().showPriceConsentIfNeeded(); - expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, true); + expect(fakeStoreKitPlatform.showPriceConsent, true); }); }); @@ -192,10 +188,6 @@ void main() { } class FakeStoreKitPlatform implements TestInAppPurchaseApi { - FakeStoreKitPlatform() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, onMethodCall); - } // get product request List startProductRequestParam = []; bool getProductRequestFailTest = false; @@ -205,7 +197,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { bool getReceiptFailTest = false; // refresh receipt request - int refreshReceipt = 0; + int refreshReceiptCount = 0; late Map refreshReceiptParam; // payment queue @@ -217,7 +209,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { bool presentCodeRedemption = false; // show price consent sheet - bool showPriceConsentIfNeeded = false; + bool showPriceConsent = false; // indicate if the payment queue delegate is registered bool isPaymentQueueDelegateRegistered = false; @@ -225,39 +217,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { // Listen to purchase updates bool? queueIsActive; - Future onMethodCall(MethodCall call) { - switch (call.method) { - // request makers - case '-[InAppPurchasePlugin refreshReceipt:result:]': - refreshReceipt++; - refreshReceiptParam = Map.castFrom( - call.arguments as Map); - return Future.sync(() {}); - // receipt manager - case '-[InAppPurchasePlugin retrieveReceiptData:result:]': - if (getReceiptFailTest) { - throw Exception('some arbitrary error'); - } - return Future.value('receipt data'); - case '-[SKPaymentQueue startObservingTransactionQueue]': - queueIsActive = true; - return Future.sync(() {}); - case '-[SKPaymentQueue stopObservingTransactionQueue]': - queueIsActive = false; - return Future.sync(() {}); - case '-[SKPaymentQueue registerDelegate]': - isPaymentQueueDelegateRegistered = true; - return Future.sync(() {}); - case '-[SKPaymentQueue removeDelegate]': - isPaymentQueueDelegateRegistered = false; - return Future.sync(() {}); - case '-[SKPaymentQueue showPriceConsentIfNeeded]': - showPriceConsentIfNeeded = true; - return Future.sync(() {}); - } - return Future.error('method not mocked'); - } - @override void addPayment(Map paymentMap) { payments @@ -304,6 +263,47 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { } return Future.value(dummyProductResponseMessage); } + + @override + void registerPaymentQueueDelegate() { + isPaymentQueueDelegateRegistered = true; + } + + @override + void removePaymentQueueDelegate() { + isPaymentQueueDelegateRegistered = false; + } + + @override + void startObservingPaymentQueue() { + queueIsActive = true; + } + + @override + void stopObservingPaymentQueue() { + queueIsActive = false; + } + + @override + String retrieveReceiptData() { + if (getReceiptFailTest) { + throw Exception('some arbitrary error'); + } + return 'receipt data'; + } + + @override + Future refreshReceipt({Map? receiptProperties}) { + refreshReceiptCount++; + refreshReceiptParam = + Map.castFrom(receiptProperties!); + return Future.sync(() {}); + } + + @override + void showPriceConsentIfNeeded() { + showPriceConsent = true; + } } class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart index 6c23da9cb495..03c9fda381cb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart @@ -4,18 +4,18 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; +import '../fakes/fake_storekit_platform.dart'; +import '../test_api.g.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); + TestInAppPurchaseApi.setup(fakeStoreKitPlatform); }); test( @@ -146,25 +146,3 @@ class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper { return false; } } - -class FakeStoreKitPlatform { - FakeStoreKitPlatform() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, onMethodCall); - } - - // indicate if the payment queue delegate is registered - bool isPaymentQueueDelegateRegistered = false; - - Future onMethodCall(MethodCall call) { - switch (call.method) { - case '-[SKPaymentQueue registerDelegate]': - isPaymentQueueDelegateRegistered = true; - return Future.sync(() {}); - case '-[SKPaymentQueue removeDelegate]': - isPaymentQueueDelegateRegistered = false; - return Future.sync(() {}); - } - return Future.error('method not mocked'); - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart index 80debcebb79d..65cd6e0bcc2d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart @@ -1,3 +1,6 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. // Autogenerated from Pigeon (v16.0.4), 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, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -102,6 +105,20 @@ abstract class TestInAppPurchaseApi { void presentCodeRedemptionSheet(); + String? retrieveReceiptData(); + + Future refreshReceipt({Map? receiptProperties}); + + void startObservingPaymentQueue(); + + void stopObservingPaymentQueue(); + + void registerPaymentQueueDelegate(); + + void removePaymentQueueDelegate(); + + void showPriceConsentIfNeeded(); + static void setup(TestInAppPurchaseApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -331,5 +348,185 @@ abstract class TestInAppPurchaseApi { }); } } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + final String? output = api.retrieveReceiptData(); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt was null.'); + final List args = (message as List?)!; + final Map? arg_receiptProperties = + (args[0] as Map?)?.cast(); + try { + await api.refreshReceipt(receiptProperties: arg_receiptProperties); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.startObservingPaymentQueue(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.stopObservingPaymentQueue(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.registerPaymentQueueDelegate(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.removePaymentQueueDelegate(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.showPriceConsentIfNeeded(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } diff --git a/packages/interactive_media_ads/AUTHORS b/packages/interactive_media_ads/AUTHORS new file mode 100644 index 000000000000..557dff97933b --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/CHANGELOG.md b/packages/interactive_media_ads/CHANGELOG.md new file mode 100644 index 000000000000..477158a8871e --- /dev/null +++ b/packages/interactive_media_ads/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Adds platform interface for Android and iOS. diff --git a/packages/interactive_media_ads/LICENSE b/packages/interactive_media_ads/LICENSE new file mode 100644 index 000000000000..c6823b81eb84 --- /dev/null +++ b/packages/interactive_media_ads/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/interactive_media_ads/README.md b/packages/interactive_media_ads/README.md new file mode 100644 index 000000000000..d1d17151f02b --- /dev/null +++ b/packages/interactive_media_ads/README.md @@ -0,0 +1,15 @@ +# interactive\_media\_ads + +Flutter plugin for the [Interactive Media Ads SDKs][1]. + +[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/interactive_media_ads) + +A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. + +| | Android | iOS | +|-------------|---------|-------| +| **Support** | SDK 19+ | 12.0+ | + +**This package is still in development.** + +[1]: https://developers.google.com/interactive-media-ads diff --git a/packages/interactive_media_ads/android/build.gradle b/packages/interactive_media_ads/android/build.gradle new file mode 100644 index 000000000000..3490e07ba38e --- /dev/null +++ b/packages/interactive_media_ads/android/build.gradle @@ -0,0 +1,78 @@ +group 'dev.flutter.packages.interactive_media_ads' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.0.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'dev.flutter.packages.interactive_media_ads' + } + + compileSdk 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdk 19 + } + + dependencies { + implementation 'androidx.annotation:annotation:1.5.0' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-inline:5.1.0' + testImplementation 'androidx.test:core:1.3.0' + } + + lintOptions { + checkAllWarnings true + warningsAsErrors true + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' + } + + 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/interactive_media_ads/android/settings.gradle b/packages/interactive_media_ads/android/settings.gradle new file mode 100644 index 000000000000..388e84d5a359 --- /dev/null +++ b/packages/interactive_media_ads/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'interactive_media_ads' diff --git a/packages/interactive_media_ads/android/src/main/AndroidManifest.xml b/packages/interactive_media_ads/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..cde3df78e2ba --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt new file mode 100644 index 000000000000..50595236588a --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** InteractiveMediaAdsPlugin */ +class InteractiveMediaAdsPlugin : FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "interactive_media_ads") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt new file mode 100644 index 000000000000..3adc0d0a56b4 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt @@ -0,0 +1,31 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class InteractiveMediaAdsPluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = InteractiveMediaAdsPlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/packages/interactive_media_ads/example/README.md b/packages/interactive_media_ads/example/README.md new file mode 100644 index 000000000000..96b8bb17dbff --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/build.gradle b/packages/interactive_media_ads/example/android/app/build.gradle new file mode 100644 index 000000000000..f3018aafa7a5 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/build.gradle @@ -0,0 +1,69 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.packages.interactive_media_ads_example" + compileSdk flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + applicationId "dev.flutter.packages.interactive_media_ads_example" + minSdk flutter.minSdkVersion + targetSdk flutter.targetSdkVersion + multiDexEnabled true + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +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' +} diff --git a/packages/interactive_media_ads/example/android/app/src/androidTest/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivityTest.kt b/packages/interactive_media_ads/example/android/app/src/androidTest/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivityTest.kt new file mode 100644 index 000000000000..7f51c3fccf13 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/androidTest/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivityTest.kt @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads_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/interactive_media_ads/example/android/app/src/debug/AndroidManifest.xml b/packages/interactive_media_ads/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000000..4665eaa3ae96 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml b/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..8733f2b862ab --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/DriverExtensionActivity.kt b/packages/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/DriverExtensionActivity.kt new file mode 100644 index 000000000000..d43924d0054f --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/DriverExtensionActivity.kt @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads_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/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivity.kt b/packages/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivity.kt new file mode 100644 index 000000000000..3392748b8720 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/kotlin/dev/flutter/packages/interactive_media_ads_example/MainActivity.kt @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/interactive_media_ads/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt b/packages/interactive_media_ads/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt new file mode 100644 index 000000000000..099fb761cc9d --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/kotlin/io/flutter/plugins/DartIntegrationTest.kt @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +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/wiki/Plugin-Tests#enabling-android-ui-tests + * for more infomation. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +annotation class DartIntegrationTest diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/interactive_media_ads/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000000..f74085f3f6a2 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/drawable/launch_background.xml b/packages/interactive_media_ads/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/interactive_media_ads/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/interactive_media_ads/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/values-night/styles.xml b/packages/interactive_media_ads/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000000..06952be745f9 --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/interactive_media_ads/example/android/app/src/main/res/values/styles.xml b/packages/interactive_media_ads/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..cb1ef88056ed --- /dev/null +++ b/packages/interactive_media_ads/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/interactive_media_ads/example/android/build.gradle b/packages/interactive_media_ads/example/android/build.gradle new file mode 100644 index 000000000000..29a592fd9d6d --- /dev/null +++ b/packages/interactive_media_ads/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/wiki/Plugins-and-Packages-repository-structure#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(":interactive_media_ads") { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "-Werror" + } + } +} diff --git a/packages/interactive_media_ads/example/android/gradle.properties b/packages/interactive_media_ads/example/android/gradle.properties new file mode 100644 index 000000000000..3b5b324f6e3f --- /dev/null +++ b/packages/interactive_media_ads/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/interactive_media_ads/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/interactive_media_ads/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..e1ca574ef017 --- /dev/null +++ b/packages/interactive_media_ads/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-7.6.3-all.zip diff --git a/packages/interactive_media_ads/example/android/settings.gradle b/packages/interactive_media_ads/example/android/settings.gradle new file mode 100644 index 000000000000..12cfac56b468 --- /dev/null +++ b/packages/interactive_media_ads/example/android/settings.gradle @@ -0,0 +1,39 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +buildscript { + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" + +apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/interactive_media_ads/example/integration_test/interactive_media_ads_test.dart b/packages/interactive_media_ads/example/integration_test/interactive_media_ads_test.dart new file mode 100644 index 000000000000..d79165b3788b --- /dev/null +++ b/packages/interactive_media_ads/example/integration_test/interactive_media_ads_test.dart @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:interactive_media_ads_example/main.dart' as app; + +/// 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/interactive_media_ads/example/ios/Flutter/AppFrameworkInfo.plist b/packages/interactive_media_ads/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000000..7c5696400627 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/interactive_media_ads/example/ios/Flutter/Debug.xcconfig b/packages/interactive_media_ads/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..ec97fc6f3021 --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Flutter/Release.xcconfig b/packages/interactive_media_ads/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..c4855bfe2000 --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Podfile b/packages/interactive_media_ads/example/ios/Podfile new file mode 100644 index 000000000000..d97f17e223fb --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.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! + use_modular_headers! + + 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/interactive_media_ads/example/ios/Runner.xcodeproj/project.pbxproj b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..966e3a40fea7 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 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 */; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + 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 */, + ); + 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 */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + 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 = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + 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"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m 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 = 12.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 = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.interactiveMediaAdsExample; + 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; + 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.interactiveMediaAdsExample.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; + 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.interactiveMediaAdsExample.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; + 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.interactiveMediaAdsExample.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 = 12.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 = 12.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 = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.interactiveMediaAdsExample; + 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 = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.packages.interactiveMediaAdsExample; + 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/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..919434a6254f --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..8e3ca5dfe193 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..1d526a16ed0f --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/interactive_media_ads/example/ios/Runner/AppDelegate.swift b/packages/interactive_media_ads/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000000..d83c0ff0beea --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..d36b1fab2d9d --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/interactive_media_ads/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/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/interactive_media_ads/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/interactive_media_ads/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/interactive_media_ads/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000..f2e259c7c939 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/interactive_media_ads/example/ios/Runner/Base.lproj/Main.storyboard b/packages/interactive_media_ads/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/interactive_media_ads/example/ios/Runner/Info.plist b/packages/interactive_media_ads/example/ios/Runner/Info.plist new file mode 100644 index 000000000000..5394f403367c --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Interactive Media Ads + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + interactive_media_ads_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/interactive_media_ads/example/ios/Runner/Runner-Bridging-Header.h b/packages/interactive_media_ads/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000000..eb7e8ba8052f --- /dev/null +++ b/packages/interactive_media_ads/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "GeneratedPluginRegistrant.h" diff --git a/packages/interactive_media_ads/example/ios/RunnerTests/RunnerTests.swift b/packages/interactive_media_ads/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000000..cea3cbd3a1fb --- /dev/null +++ b/packages/interactive_media_ads/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit +import XCTest + +@testable import interactive_media_ads + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = InteractiveMediaAdsPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/packages/interactive_media_ads/example/lib/main.dart b/packages/interactive_media_ads/example/lib/main.dart new file mode 100644 index 000000000000..fd385e0a60c3 --- /dev/null +++ b/packages/interactive_media_ads/example/lib/main.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; + +/// Entry point for integration tests that require espresso. +@pragma('vm:entry-point') +void integrationTestMain() { + enableFlutterDriverExtension(); + main(); +} + +void main() { + runApp(const MyApp()); +} + +/// Home widget of the example app. +class MyApp extends StatefulWidget { + /// Constructs [MyApp]. + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + debugPrint('THEAPP'); + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $defaultTargetPlatform'), + ), + ), + ); + } +} diff --git a/packages/interactive_media_ads/example/pubspec.yaml b/packages/interactive_media_ads/example/pubspec.yaml new file mode 100644 index 000000000000..fde3e7a8b2ec --- /dev/null +++ b/packages/interactive_media_ads/example/pubspec.yaml @@ -0,0 +1,25 @@ +name: interactive_media_ads_example +description: "Demonstrates how to use the interactive_media_ads plugin." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ^3.2.3 + flutter: ">=3.16.6" + +dependencies: + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + interactive_media_ads: + path: ../ + +dev_dependencies: + espresso: ^0.2.0 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/packages/interactive_media_ads/example/test_driver/integration_test.dart b/packages/interactive_media_ads/example/test_driver/integration_test.dart new file mode 100644 index 000000000000..4f10f2a522f3 --- /dev/null +++ b/packages/interactive_media_ads/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/interactive_media_ads/ios/Assets/.gitkeep b/packages/interactive_media_ads/ios/Assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/interactive_media_ads/ios/Classes/InteractiveMediaAdsPlugin.swift b/packages/interactive_media_ads/ios/Classes/InteractiveMediaAdsPlugin.swift new file mode 100644 index 000000000000..5b94fd6a24aa --- /dev/null +++ b/packages/interactive_media_ads/ios/Classes/InteractiveMediaAdsPlugin.swift @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit + +public class InteractiveMediaAdsPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "interactive_media_ads", binaryMessenger: registrar.messenger()) + let instance = InteractiveMediaAdsPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/packages/interactive_media_ads/ios/Resources/PrivacyInfo.xcprivacy b/packages/interactive_media_ads/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000000..5516ebf30d18 --- /dev/null +++ b/packages/interactive_media_ads/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/interactive_media_ads/ios/interactive_media_ads.podspec b/packages/interactive_media_ads/ios/interactive_media_ads.podspec new file mode 100644 index 000000000000..8980d486c212 --- /dev/null +++ b/packages/interactive_media_ads/ios/interactive_media_ads.podspec @@ -0,0 +1,29 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint interactive_media_ads.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'interactive_media_ads' + s.version = '0.0.1' + s.summary = 'A plugin for Interactive Media Ads SDKs.' + s.description = <<-DESC +A Flutter plugin for using the Interactive Media Ads SDKs. +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/interactive_media_ads/interactive_media_ads' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '12.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + 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' + s.resource_bundles = {'interactive_media_ads_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/packages/interactive_media_ads/lib/interactive_media_ads.dart b/packages/interactive_media_ads/lib/interactive_media_ads.dart new file mode 100644 index 000000000000..185d304cf5bc --- /dev/null +++ b/packages/interactive_media_ads/lib/interactive_media_ads.dart @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/ad_display_container.dart'; +export 'src/ads_loader.dart'; +export 'src/ads_manager_delegate.dart'; +export 'src/platform_interface/ad_error.dart'; +export 'src/platform_interface/ad_event.dart'; +export 'src/platform_interface/ads_request.dart'; diff --git a/packages/interactive_media_ads/lib/src/ad_display_container.dart b/packages/interactive_media_ads/lib/src/ad_display_container.dart new file mode 100644 index 000000000000..99da19682b8e --- /dev/null +++ b/packages/interactive_media_ads/lib/src/ad_display_container.dart @@ -0,0 +1,102 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'platform_interface/platform_ad_display_container.dart'; +import 'platform_interface/platform_interface.dart'; + +/// Handles playing ads after they've been received from the server. +/// +/// ## 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 interactive_media_ads.AdDisplayContainer.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final AdDisplayContainer container = AdDisplayContainer(); +/// +/// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { +/// final IOSAdDisplayContainer iosContainer = container.platform as IOSAdDisplayContainer; +/// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { +/// final AndroidAdDisplayContainer androidContainer = +/// container.platform as AndroidAdDisplayContainer; +/// } +/// ``` +class AdDisplayContainer extends StatelessWidget { + /// Constructs an [AdDisplayContainer]. + /// + /// See [AdDisplayContainer.fromPlatformCreationParams] for setting parameters for a + /// specific platform. + AdDisplayContainer({ + Key? key, + required void Function(AdDisplayContainer container) onContainerAdded, + }) : this.fromPlatformCreationParams( + key: key, + params: PlatformAdDisplayContainerCreationParams( + onContainerAdded: (PlatformAdDisplayContainer container) { + onContainerAdded(AdDisplayContainer.fromPlatform( + platform: container, + )); + }, + ), + ); + + /// Constructs an [AdDisplayContainer] from creation params for a specific platform. + /// + /// {@template interactive_media_ads.AdDisplayContainer.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformAdDisplayContainerCreationParams params = + /// const PlatformAdDisplayContainerCreationParams(); + /// + /// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { + /// params = IOSAdDisplayContainerCreationParams + /// .fromPlatformAdDisplayContainerCreationParams( + /// params, + /// ); + /// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { + /// params = AndroidAdDisplayContainerCreationParams + /// .fromPlatformAdDisplayContainerCreationParams( + /// params, + /// ); + /// } + /// + /// final AdDisplayContainer container = AdDisplayContainer.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + AdDisplayContainer.fromPlatformCreationParams({ + Key? key, + required PlatformAdDisplayContainerCreationParams params, + }) : this.fromPlatform( + key: key, + platform: PlatformAdDisplayContainer(params), + ); + + /// Constructs an [AdDisplayContainer] from a specific platform + /// implementation. + const AdDisplayContainer.fromPlatform({super.key, required this.platform}); + + /// Implementation of [PlatformAdDisplayContainer] for the current platform. + final PlatformAdDisplayContainer platform; + + /// Invoked when the native view that contains the ad has been added to the + /// platform view hierarchy. + void Function(PlatformAdDisplayContainer container) get onContainerAdded => + platform.params.onContainerAdded; + + @override + Widget build(BuildContext context) { + return platform.build(context); + } +} diff --git a/packages/interactive_media_ads/lib/src/ads_loader.dart b/packages/interactive_media_ads/lib/src/ads_loader.dart new file mode 100644 index 000000000000..9370cc9fec35 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/ads_loader.dart @@ -0,0 +1,162 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import 'ad_display_container.dart'; +import 'ads_manager_delegate.dart'; +import 'platform_interface/platform_interface.dart'; + +/// Handles playing ads after they've been received from the server. +/// +/// ## 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 interactive_media_ads.AdsLoader.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final AdsLoader loader = AdsLoader(); +/// +/// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { +/// final IOSAdsLoader iosLoader = loader.platform as IOSAdsLoader; +/// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { +/// final AndroidAdsLoader androidLoader = +/// loader.platform as AndroidAdsLoader; +/// } +/// ``` +class AdsLoader { + /// Constructs an [AdsLoader]. + /// + /// See [AdsLoader.fromPlatformCreationParams] for setting parameters for a + /// specific platform. + AdsLoader({ + required AdDisplayContainer container, + required void Function(OnAdsLoadedData data) onAdsLoaded, + required void Function(AdsLoadErrorData data) onAdsLoadError, + }) : this.fromPlatformCreationParams( + PlatformAdsLoaderCreationParams( + container: container.platform, + onAdsLoaded: (PlatformOnAdsLoadedData data) { + onAdsLoaded(OnAdsLoadedData._(platform: data)); + }, + onAdsLoadError: onAdsLoadError, + ), + ); + + /// Constructs an [AdsLoader] from creation params for a specific platform. + /// + /// {@template interactive_media_ads.AdsLoader.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformAdsLoaderCreationParams params = + /// const PlatformAdsLoaderCreationParams(); + /// + /// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { + /// params = IOSAdsLoaderCreationParams + /// .fromPlatformAdsLoaderCreationParams( + /// params, + /// ); + /// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { + /// params = AndroidAdsLoaderCreationParams + /// .fromPlatformAdsLoaderCreationParams( + /// params, + /// ); + /// } + /// + /// final AdsLoader loader = AdsLoader.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + AdsLoader.fromPlatformCreationParams( + PlatformAdsLoaderCreationParams params, + ) : this.fromPlatform(PlatformAdsLoader(params)); + + /// Constructs a [AdsLoader] from a specific platform implementation. + AdsLoader.fromPlatform(this.platform); + + /// Implementation of [PlatformAdsLoader] for the current platform. + final PlatformAdsLoader platform; + + /// Signals to the SDK that the content has completed. + Future contentComplete() { + return platform.contentComplete(); + } + + /// Requests ads from a server. + Future requestAds(AdsRequest request) { + return platform.requestAds(request); + } +} + +/// Data when ads are successfully loaded from the ad server through an +/// [AdsLoader]. +@immutable +class OnAdsLoadedData { + OnAdsLoadedData._({required this.platform}); + + /// Implementation of [PlatformOnAdsLoadedData] for the current platform. + final PlatformOnAdsLoadedData platform; + + /// The ads manager instance created by the ads loader. + late final AdsManager manager = AdsManager._fromPlatform(platform.manager); +} + +/// Handles playing ads after they've been received from the server. +/// +/// ## 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 interactive_media_ads.AdsManager.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final AdsManager manager = AdsManager(); +/// +/// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { +/// final IOSAdsManager iosManager = manager.platform as IOSAdsManager; +/// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { +/// final AndroidAdsManager androidManager = +/// manager.platform as AndroidAdsManager; +/// } +/// ``` +class AdsManager { + /// Constructs a [AdsManager] from a specific platform implementation. + AdsManager._fromPlatform(this.platform); + + /// Implementation of [PlatformAdsManager] for the current platform. + final PlatformAdsManager platform; + + /// Initializes the ad experience using default rendering settings. + Future init() { + return platform.init(AdsManagerInitParams()); + } + + /// Starts playing the ads. + Future start() { + return platform.start(AdsManagerStartParams()); + } + + /// The [AdsManagerDelegate] to notify with events during ad playback. + Future setAdsManagerDelegate(AdsManagerDelegate delegate) { + return platform.setAdsManagerDelegate(delegate.platform); + } + + /// Stops the ad and all tracking, then releases all assets that were loaded + /// to play the ad. + Future destroy() { + return platform.destroy(); + } +} diff --git a/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart new file mode 100644 index 000000000000..4a3813c719af --- /dev/null +++ b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'platform_interface/platform_interface.dart'; + +/// Handles playing ads after they've been received from the server. +/// +/// ## 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 interactive_media_ads.AdsManagerDelegate.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final AdsManagerDelegate delegate = AdsManagerDelegate(); +/// +/// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { +/// final IOSAdsManagerDelegate iosDelegate = delegate.platform as IOSAdsManagerDelegate; +/// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { +/// final AndroidAdsManagerDelegate androidDelegate = +/// delegate.platform as AndroidAdsManagerDelegate; +/// } +/// ``` +class AdsManagerDelegate { + /// Constructs an [AdsManagerDelegate]. + /// + /// See [AdsManagerDelegate.fromPlatformCreationParams] for setting parameters for a + /// specific platform. + AdsManagerDelegate({ + void Function(AdEvent event)? onAdEvent, + void Function(AdErrorEvent event)? onAdErrorEvent, + }) : this.fromPlatformCreationParams( + PlatformAdsManagerDelegateCreationParams( + onAdEvent: onAdEvent, + onAdErrorEvent: onAdErrorEvent, + ), + ); + + /// Constructs an [AdsManagerDelegate] from creation params for a specific platform. + /// + /// {@template interactive_media_ads.AdsManagerDelegate.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformAdsManagerDelegateCreationParams params = + /// const PlatformAdsManagerDelegateCreationParams(); + /// + /// if (InteractiveMediaAdsPlatform.instance is IOSInteractiveMediaAdsPlatform) { + /// params = IOSAdsManagerDelegateCreationParams + /// .fromPlatformAdsManagerDelegateCreationParams( + /// params, + /// ); + /// } else if (InteractiveMediaAdsPlatform.instance is AndroidInteractiveMediaAdsPlatform) { + /// params = AndroidAdsManagerDelegateCreationParams + /// .fromPlatformAdsManagerDelegateCreationParams( + /// params, + /// ); + /// } + /// + /// final AdsManagerDelegate delegate = AdsManagerDelegate.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + AdsManagerDelegate.fromPlatformCreationParams( + PlatformAdsManagerDelegateCreationParams params, + ) : this.fromPlatform(PlatformAdsManagerDelegate(params)); + + /// Constructs a [AdsManagerDelegate] from a specific platform implementation. + AdsManagerDelegate.fromPlatform(this.platform); + + /// Implementation of [PlatformAdsManagerDelegate] for the current platform. + final PlatformAdsManagerDelegate platform; + + /// Invoked when there is an [AdEvent]. + void Function(AdEvent event)? get onAdEvent => platform.params.onAdEvent; + + /// Invoked when there was an error playing the ad. Log the error and resume + /// playing content. + void Function(AdErrorEvent event)? get onAdErrorEvent => + platform.params.onAdErrorEvent; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/ad_error.dart b/packages/interactive_media_ads/lib/src/platform_interface/ad_error.dart new file mode 100644 index 000000000000..ca89e967307b --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/ad_error.dart @@ -0,0 +1,156 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +/// The types of errors that can be encountered when loading an ad. +enum AdErrorCode { + /// Generic invalid usage of the API. + apiError, + + /// Ads player was not provided. + adsPlayerNotProvided, + + /// There was a problem requesting ads from the server. + adsRequestNetworkError, + + /// The ad slot is not visible on the page. + adslotNotVisible, + + /// There was a problem requesting ads from the server. + companionAdLoadingFailed, + + /// Content playhead was not passed in, but list of ads has been returned from + /// the server. + contentPlayheadMissing, + + /// There was a problem requesting ads from the server. + failedToRequestAds, + + /// There was an error loading the ad. + failedLoadingAd, + + /// An error internal to the SDK occurred. + /// + /// More information may be available in the details. + internalError, + + /// Invalid arguments were provided to SDK methods. + invalidArguments, + + /// The version of the runtime is too old. + osRuntimeTooOld, + + /// An overlay ad failed to load. + overlayAdLoadingFailed, + + /// An overlay ad failed to render. + overlayAdPlayingFailed, + + /// Ads list was returned but ContentProgressProvider was not configured. + playlistNoContentTracking, + + /// Ads list response was malformed. + playlistMalformedResponse, + + /// Listener for at least one of the required vast events was not added. + requiredListenersNotAdded, + + /// There was an error initializing the stream. + streamInitializationFailed, + + /// Ads loader sent ads loaded event when it was not expected. + unexpectedAdsLoadedEvent, + + /// The ad response was not understood and cannot be parsed. + unknownAdResponse, + + /// An unexpected error occurred and the cause is not known. + /// + /// Refer to the inner error for more information. + unknownError, + + /// No assets were found in the VAST ad response. + vastAssetNotFound, + + /// A VAST response containing a single tag with no child tags. + vastEmptyResponse, + + /// Assets were found in the VAST ad response for a linear ad, but none of + /// them matched the video player's capabilities. + vastLinearAssetMismatch, + + /// At least one VAST wrapper ad loaded successfully and a subsequent wrapper + /// or inline ad load has timed out. + vastLoadTimeout, + + /// The ad response was not recognized as a valid VAST ad. + vastMalformedResponse, + + /// Failed to load media assets from a VAST response. + /// + /// The default timeout for media loading is 8 seconds. + vastMediaLoadTimeout, + + /// Assets were found in the VAST ad response for a nonlinear ad, but none of + /// them matched the video player's capabilities. + vastNonlinearAssetMismatch, + + /// No Ads VAST response after one or more wrappers. + vastNoAdsAfterWrapper, + + /// The maximum number of VAST wrapper redirects has been reached. + vastTooManyRedirects, + + /// Trafficking error. + /// + /// Video player received an ad type that it was not expecting and/or cannot + /// display. + vastTraffickingError, + + /// At least one VAST wrapper loaded and a subsequent wrapper or inline ad + /// load has resulted in a 404 response code. + vastInvalidUrl, + + /// There was an error playing the video ad. + videoPlayError, + + /// Another VideoAdsManager is still using the video. + /// + /// It must be unloaded before another ad can play on the same element. + videoElementUsed, + + /// A video element was not specified where it was required. + videoElementRequired, +} + +/// Possible error types while loading or playing ads. +enum AdErrorType { + /// Indicates an error occurred while loading the ads. + loading, + + /// Indicates an error occurred while playing the ads. + playing, + + /// An unexpected error occurred while loading or playing the ads. + /// + /// This may mean that the SDK wasn’t loaded properly. + unknown, +} + +/// Surfaces an error that occurred during ad loading or playing. +@immutable +class AdError { + /// Creates a [AdError]. + const AdError({required this.type, required this.code, this.message}); + + /// Specifies the source of the error. + final AdErrorType type; + + /// The error code for obtaining more specific information about the error. + final AdErrorCode code; + + /// A brief description about the error. + final String? message; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart b/packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart new file mode 100644 index 000000000000..9824d9915fb3 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/ad_event.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import 'ad_error.dart'; + +/// Types of events that can occur during ad playback. +enum AdEventType { + /// Fired when the ads manager is done playing all the valid ads in the ads + /// response, or when the response doesn't return any valid ads. + allAdsCompleted, + + /// Fired when an ad is clicked. + clicked, + + /// Fired when an ad completes playing. + complete, + + /// Fired when content should be paused. + /// + /// This usually happens right before an ad is about to hide the content. + contentPauseRequested, + + /// Fired when content should be resumed. + /// + /// This usually happens when an ad finishes or collapses. + contentResumeRequested, + + /// Fired when the VAST response has been received. + loaded, +} + +/// Simple data class used to transport ad playback information. +@immutable +class AdEvent { + /// Creates an [AdEvent]. + const AdEvent({required this.type}); + + /// The type of event that occurred. + final AdEventType type; +} + +/// An event raised when there is an error loading or playing ads. +@immutable +class AdErrorEvent { + /// Creates an [AdErrorEvent]. + const AdErrorEvent({required this.error}); + + /// The error that caused this event. + final AdError error; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/ads_request.dart b/packages/interactive_media_ads/lib/src/platform_interface/ads_request.dart new file mode 100644 index 000000000000..72e4dfc69b63 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/ads_request.dart @@ -0,0 +1,12 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// An object containing the data used to request ads from the server. +class AdsRequest { + /// Creates an [AdsRequest]. + AdsRequest({required this.adTagUrl}); + + /// The URL from which ads will be requested. + final String adTagUrl; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/interactive_media_ads_platform.dart b/packages/interactive_media_ads/lib/src/platform_interface/interactive_media_ads_platform.dart new file mode 100644 index 000000000000..f1dac2db7d1d --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/interactive_media_ads_platform.dart @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'platform_ad_display_container.dart'; +import 'platform_ads_loader.dart'; +import 'platform_ads_manager_delegate.dart'; + +/// Interface for a platform implementation of the Interactive Media Ads SDKs. +abstract base class InteractiveMediaAdsPlatform { + /// The instance of [InteractiveMediaAdsPlatform] to use. + /// + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [InteractiveMediaAdsPlatform] when they register + /// themselves. + static InteractiveMediaAdsPlatform? instance; + + /// Creates a new [PlatformAdsLoader]. + PlatformAdsLoader createPlatformAdsLoader( + PlatformAdsLoaderCreationParams params, + ); + + /// Creates a new [PlatformAdsManagerDelegate]. + PlatformAdsManagerDelegate createPlatformAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams params, + ); + + /// Creates a new [PlatformAdDisplayContainer]. + PlatformAdDisplayContainer createPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams params, + ); +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_display_container.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_display_container.dart new file mode 100644 index 000000000000..075706f39307 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ad_display_container.dart @@ -0,0 +1,95 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'interactive_media_ads_platform.dart'; + +/// Object specifying creation parameters for creating a +/// [PlatformAdDisplayContainer]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the +/// [PlatformAdDisplayContainerCreationParams] to provide additional platform +/// specific parameters. +/// +/// When extending [PlatformAdDisplayContainerCreationParams] additional +/// parameters should always accept `null` or have a default value to prevent +/// breaking changes. +/// +/// ```dart +/// class AndroidPlatformAdDisplayContainerCreationParams +/// extends PlatformAdDisplayContainerCreationParams { +/// AndroidPlatformAdDisplayContainerCreationParams._( +/// PlatformAdDisplayContainerCreationParams params, { +/// this.uri, +/// }) : super(); +/// +/// factory AndroidAdDisplayContainerCreationParams.fromPlatformAdDisplayContainerCreationParams( +/// PlatformAdDisplayContainerCreationParams params, { +/// Uri? uri, +/// }) { +/// return AndroidAdDisplayContainerCreationParams._(params, uri: uri); +/// } +/// +/// final Uri? uri; +/// } +/// ``` +@immutable +base class PlatformAdDisplayContainerCreationParams { + /// Used by the platform implementation to create a new + /// [PlatformAdDisplayContainer]. + const PlatformAdDisplayContainerCreationParams({ + this.key, + required this.onContainerAdded, + }); + + /// Controls how one widget replaces another widget in the tree. + /// + /// See also: + /// * The discussions at [Key] and [GlobalKey]. + final Key? key; + + /// Invoked when the View that contains the ad has been added to the platform + /// view hierarchy. + final void Function(PlatformAdDisplayContainer container) onContainerAdded; +} + +/// The interface for a platform implementation for a container in which to +/// display ads. +abstract base class PlatformAdDisplayContainer { + /// Creates a new [PlatformAdDisplayContainer] + factory PlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams params, + ) { + assert( + InteractiveMediaAdsPlatform.instance != null, + 'A platform implementation for `interactive_media_ads` has not been set. ' + 'Please ensure that an implementation of `InteractiveMediaAdsPlatform` ' + 'has been set to `InteractiveMediaAdsPlatform.instance` before use. For ' + 'unit testing, `InteractiveMediaAdsPlatform.instance` can be set with ' + 'your own test implementation.', + ); + final PlatformAdDisplayContainer implementation = + InteractiveMediaAdsPlatform.instance! + .createPlatformAdDisplayContainer(params); + return implementation; + } + + /// Used by the platform implementation to create a new + /// [PlatformAdDisplayContainer]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformAdDisplayContainer.implementation(this.params); + + /// The parameters used to initialize the [PlatformAdDisplayContainer]. + final PlatformAdDisplayContainerCreationParams params; + + /// Builds the Widget that contains the native View. + Widget build(BuildContext context); +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_loader.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_loader.dart new file mode 100644 index 000000000000..7dc6a57a1d22 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_loader.dart @@ -0,0 +1,119 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import 'ad_error.dart'; +import 'ads_request.dart'; +import 'interactive_media_ads_platform.dart'; +import 'platform_ad_display_container.dart'; +import 'platform_ads_manager.dart'; + +/// Object specifying creation parameters for creating a [PlatformAdsLoader]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the +/// [PlatformAdsLoaderCreationParams] to provide additional platform specific +/// parameters. +/// +/// When extending [PlatformAdsLoaderCreationParams] additional parameters +/// should always accept `null` or have a default value to prevent breaking +/// changes. +/// +/// ```dart +/// class AndroidPlatformAdsLoaderCreationParams +/// extends PlatformAdsLoaderCreationParams { +/// AndroidPlatformAdsLoaderCreationParams._( +/// PlatformAdsLoaderCreationParams params, { +/// this.uri, +/// }) : super(); +/// +/// factory AndroidAdsLoaderCreationParams.fromPlatformAdsLoaderCreationParams( +/// PlatformAdsLoaderCreationParams params, { +/// Uri? uri, +/// }) { +/// return AndroidAdsLoaderCreationParams._(params, uri: uri); +/// } +/// +/// final Uri? uri; +/// } +/// ``` +@immutable +base class PlatformAdsLoaderCreationParams { + /// Used by the platform implementation to create a new [PlatformAdsLoader]. + const PlatformAdsLoaderCreationParams({ + required this.container, + required this.onAdsLoaded, + required this.onAdsLoadError, + }); + + /// A container object where ads are rendered. + final PlatformAdDisplayContainer container; + + /// Callback for the ads manager loaded event. + final void Function(PlatformOnAdsLoadedData data) onAdsLoaded; + + /// Callback for errors that occur during the ads request. + final void Function(AdsLoadErrorData data) onAdsLoadError; +} + +/// Interface for a platform implementation of an object that requests ads and +/// handles events from ads request responses. +abstract base class PlatformAdsLoader { + /// Creates a new [PlatformAdsLoader] + factory PlatformAdsLoader( + PlatformAdsLoaderCreationParams params, + ) { + assert( + InteractiveMediaAdsPlatform.instance != null, + 'A platform implementation for `interactive_media_ads` has not been set. ' + 'Please ensure that an implementation of `InteractiveMediaAdsPlatform` ' + 'has been set to `InteractiveMediaAdsPlatform.instance` before use. For ' + 'unit testing, `InteractiveMediaAdsPlatform.instance` can be set with ' + 'your own test implementation.', + ); + final PlatformAdsLoader implementation = + InteractiveMediaAdsPlatform.instance!.createPlatformAdsLoader(params); + return implementation; + } + + /// Used by the platform implementation to create a new [PlatformAdsLoader]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformAdsLoader.implementation(this.params); + + /// The parameters used to initialize the [PlatformAdsLoader]. + final PlatformAdsLoaderCreationParams params; + + /// Signal to the SDK that the content has completed. + Future contentComplete(); + + /// Requests ads from a server. + Future requestAds(AdsRequest request); +} + +/// Data when ads are successfully loaded from the ad server through an +/// [PlatformAdsLoader]. +@immutable +class PlatformOnAdsLoadedData { + /// Creates a [PlatformOnAdsLoadedData]. + const PlatformOnAdsLoadedData({required this.manager}); + + /// The ads manager instance created by the ads loader. + final PlatformAdsManager manager; +} + +/// Ad error data that is returned when the ads loader fails to load the ad. +@immutable +class AdsLoadErrorData { + /// Creates a [AdsLoadErrorData]. + const AdsLoadErrorData({required this.error}); + + /// The ad error that occurred while loading the ad. + final AdError error; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart new file mode 100644 index 000000000000..d80a17a6a730 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import 'platform_ads_manager_delegate.dart'; + +/// Additional parameter passed to an [PlatformAdsManager] on initialization. +base class AdsManagerInitParams {} + +/// Additional parameter passed to an [PlatformAdsManager] when starting to play +/// ads. +base class AdsManagerStartParams {} + +/// Interface for a platform implementation of a `AdsManager`. +abstract class PlatformAdsManager { + /// Creates a [PlatformAdsManager]. + @protected + PlatformAdsManager(); + + /// Initializes the ad experience using default rendering settings. + Future init(AdsManagerInitParams params); + + /// Starts playing the ads. + Future start(AdsManagerStartParams params); + + /// /// The [AdsManagerDelegate] to notify with events during ad playback. + Future setAdsManagerDelegate(PlatformAdsManagerDelegate delegate); + + /// Stops the ad and all tracking, then releases all assets that were loaded + /// to play the ad. + Future destroy(); +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart new file mode 100644 index 000000000000..146e5d1914e6 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager_delegate.dart @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; + +import 'ad_event.dart'; +import 'interactive_media_ads_platform.dart'; + +/// Object specifying creation parameters for creating a +/// [PlatformAdsManagerDelegate]. +/// +/// Platform specific implementations can add additional fields by extending +/// this class. +/// +/// This example demonstrates how to extend the +/// [PlatformAdsManagerDelegateCreationParams] to provide additional platform +/// specific parameters. +/// +/// When extending [PlatformAdsManagerDelegateCreationParams] additional +/// parameters should always accept `null` or have a default value to prevent +/// breaking changes. +/// +/// ```dart +/// class AndroidPlatformAdsManagerDelegateCreationParams +/// extends PlatformAdsManagerDelegateCreationParams { +/// AndroidPlatformAdsManagerDelegateCreationParams._( +/// PlatformAdsManagerDelegateCreationParams params, { +/// this.uri, +/// }) : super(); +/// +/// factory AndroidAdsManagerDelegateCreationParams.fromPlatformAdsManagerDelegateCreationParams( +/// PlatformAdsManagerDelegateCreationParams params, { +/// Uri? uri, +/// }) { +/// return AndroidAdsManagerDelegateCreationParams._(params, uri: uri); +/// } +/// +/// final Uri? uri; +/// } +/// ``` +@immutable +base class PlatformAdsManagerDelegateCreationParams { + /// Used by the platform implementation to create a new [PlatformAdsManagerDelegate]. + const PlatformAdsManagerDelegateCreationParams({ + this.onAdEvent, + this.onAdErrorEvent, + }); + + /// Invoked when there is an [AdEvent]. + final void Function(AdEvent event)? onAdEvent; + + /// Invoked when there was an error playing the ad. Log the error and resume + /// playing content. + final void Function(AdErrorEvent event)? onAdErrorEvent; +} + +/// Interface for a platform implementation of a `AdsManagerDelegate`. +abstract base class PlatformAdsManagerDelegate { + /// Creates a new [PlatformAdsManagerDelegate] + factory PlatformAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams params, + ) { + assert( + InteractiveMediaAdsPlatform.instance != null, + 'A platform implementation for `interactive_media_ads` has not been set. ' + 'Please ensure that an implementation of `InteractiveMediaAdsPlatform` ' + 'has been set to `InteractiveMediaAdsPlatform.instance` before use. For ' + 'unit testing, `InteractiveMediaAdsPlatform.instance` can be set with ' + 'your own test implementation.', + ); + final PlatformAdsManagerDelegate implementation = + InteractiveMediaAdsPlatform.instance! + .createPlatformAdsManagerDelegate(params); + return implementation; + } + + /// Used by the platform implementation to create a new + /// [PlatformAdsManagerDelegate]. + /// + /// Should only be used by platform implementations because they can't extend + /// a class that only contains a factory constructor. + @protected + PlatformAdsManagerDelegate.implementation(this.params); + + /// The parameters used to initialize the [PlatformAdsManagerDelegate]. + final PlatformAdsManagerDelegateCreationParams params; +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart new file mode 100644 index 000000000000..ad8896480443 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_interface.dart @@ -0,0 +1,12 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'ad_error.dart'; +export 'ad_event.dart'; +export 'ads_request.dart'; +export 'interactive_media_ads_platform.dart'; +export 'platform_ad_display_container.dart'; +export 'platform_ads_loader.dart'; +export 'platform_ads_manager.dart'; +export 'platform_ads_manager_delegate.dart'; diff --git a/packages/interactive_media_ads/pubspec.yaml b/packages/interactive_media_ads/pubspec.yaml new file mode 100644 index 000000000000..a6b7b04a2148 --- /dev/null +++ b/packages/interactive_media_ads/pubspec.yaml @@ -0,0 +1,31 @@ +name: interactive_media_ads +description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. +repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22 +version: 0.0.1 + +environment: + sdk: ^3.2.3 + flutter: ">=3.16.6" + +flutter: + plugin: + platforms: + android: + package: dev.flutter.packages.interactive_media_ads + pluginClass: InteractiveMediaAdsPlugin + ios: + pluginClass: InteractiveMediaAdsPlugin + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + build_runner: ^2.1.4 + flutter_test: + sdk: flutter + mockito: 5.4.4 + +topics: + - ads diff --git a/packages/interactive_media_ads/test/ad_display_container_test.dart b/packages/interactive_media_ads/test/ad_display_container_test.dart new file mode 100644 index 000000000000..a82a16e8c4c2 --- /dev/null +++ b/packages/interactive_media_ads/test/ad_display_container_test.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/interactive_media_ads.dart'; +import 'package:interactive_media_ads/src/ad_display_container.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; + +import 'test_stubs.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('build', (WidgetTester tester) async { + final TestPlatformAdDisplayContainer adDisplayContainer = + TestPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + ), + onBuild: (_) => Container(), + ); + + await tester.pumpWidget(AdDisplayContainer.fromPlatform( + platform: adDisplayContainer, + )); + + expect(find.byType(Container), findsOneWidget); + }); + + testWidgets('constructor parameters are correctly passed to creation params', + (WidgetTester tester) async { + InteractiveMediaAdsPlatform.instance = + TestInteractiveMediaAdsPlatform(onCreatePlatformAdDisplayContainer: ( + PlatformAdDisplayContainerCreationParams params, + ) { + return TestPlatformAdDisplayContainer( + params, + onBuild: (_) => Container(), + ); + }, onCreatePlatformAdsLoader: (PlatformAdsLoaderCreationParams params) { + throw UnimplementedError(); + }, onCreatePlatformAdsManagerDelegate: ( + PlatformAdsManagerDelegateCreationParams params, + ) { + throw UnimplementedError(); + }); + + final AdDisplayContainer adDisplayContainer = AdDisplayContainer( + key: GlobalKey(), + onContainerAdded: (_) {}, + ); + + // The key passed to the default constructor is used by the super class + // and not passed to the platform implementation. + expect(adDisplayContainer.platform.params.key, isNull); + }); +} diff --git a/packages/interactive_media_ads/test/ads_loader_test.dart b/packages/interactive_media_ads/test/ads_loader_test.dart new file mode 100644 index 000000000000..424c86ccc1d5 --- /dev/null +++ b/packages/interactive_media_ads/test/ads_loader_test.dart @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/interactive_media_ads.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; + +import 'test_stubs.dart'; + +void main() { + test('contentComplete', () async { + final TestPlatformAdsLoader adsLoader = TestPlatformAdsLoader( + PlatformAdsLoaderCreationParams( + container: createTestAdDisplayContainer(), + onAdsLoaded: (PlatformOnAdsLoadedData data) {}, + onAdsLoadError: (AdsLoadErrorData data) {}, + ), + onContentComplete: expectAsync0(() async {}), + onRequestAds: (AdsRequest request) async {}, + ); + + final AdsLoader loader = AdsLoader.fromPlatform(adsLoader); + await loader.contentComplete(); + }); + + test('requestAds', () async { + final TestPlatformAdsLoader adsLoader = TestPlatformAdsLoader( + PlatformAdsLoaderCreationParams( + container: createTestAdDisplayContainer(), + onAdsLoaded: (PlatformOnAdsLoadedData data) {}, + onAdsLoadError: (AdsLoadErrorData data) {}, + ), + onRequestAds: expectAsync1((AdsRequest request) async {}), + onContentComplete: () async {}, + ); + + final AdsLoader loader = AdsLoader.fromPlatform(adsLoader); + await loader.requestAds(AdsRequest(adTagUrl: '')); + }); +} + +TestPlatformAdDisplayContainer createTestAdDisplayContainer() { + return TestPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + ), + onBuild: (_) => Container(), + ); +} diff --git a/packages/interactive_media_ads/test/ads_manager_delegate_test.dart b/packages/interactive_media_ads/test/ads_manager_delegate_test.dart new file mode 100644 index 000000000000..ba6801dc41f3 --- /dev/null +++ b/packages/interactive_media_ads/test/ads_manager_delegate_test.dart @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/interactive_media_ads.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; + +import 'test_stubs.dart'; + +void main() { + test('passes params to platform instance', () async { + InteractiveMediaAdsPlatform.instance = TestInteractiveMediaAdsPlatform( + onCreatePlatformAdsManagerDelegate: + (PlatformAdsManagerDelegateCreationParams params) { + return TestPlatformAdsManagerDelegate(params); + }, + onCreatePlatformAdsLoader: (PlatformAdsLoaderCreationParams params) { + throw UnimplementedError(); + }, + onCreatePlatformAdDisplayContainer: + (PlatformAdDisplayContainerCreationParams params) { + throw UnimplementedError(); + }, + ); + + void onAdEvent(AdEvent event) {} + void onAdErrorEvent(AdErrorEvent event) {} + + final AdsManagerDelegate delegate = AdsManagerDelegate( + onAdEvent: onAdEvent, + onAdErrorEvent: onAdErrorEvent, + ); + + expect(delegate.platform.params.onAdEvent, onAdEvent); + expect(delegate.platform.params.onAdErrorEvent, onAdErrorEvent); + }); +} diff --git a/packages/interactive_media_ads/test/ads_manager_test.dart b/packages/interactive_media_ads/test/ads_manager_test.dart new file mode 100644 index 000000000000..cb5f42ccb3f6 --- /dev/null +++ b/packages/interactive_media_ads/test/ads_manager_test.dart @@ -0,0 +1,93 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/interactive_media_ads.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; + +import 'test_stubs.dart'; + +void main() { + test('init', () async { + final TestAdsManager platformManager = TestAdsManager( + onInit: expectAsync1((_) async {}), + ); + + final AdsManager manager = createAdsManager(platformManager); + await manager.init(); + }); + + test('start', () async { + final TestAdsManager platformManager = TestAdsManager( + onStart: expectAsync1((_) async {}), + ); + + final AdsManager manager = createAdsManager(platformManager); + await manager.start(); + }); + + test('setAdsManagerDelegate', () async { + final TestAdsManager platformManager = TestAdsManager( + onSetAdsManagerDelegate: expectAsync1((_) async {}), + ); + + final AdsManager manager = createAdsManager(platformManager); + await manager.setAdsManagerDelegate(AdsManagerDelegate.fromPlatform( + TestPlatformAdsManagerDelegate( + const PlatformAdsManagerDelegateCreationParams(), + ), + )); + }); + + test('destroy', () async { + final TestAdsManager platformManager = TestAdsManager( + onDestroy: expectAsync0(() async {}), + ); + + final AdsManager manager = createAdsManager(platformManager); + await manager.destroy(); + }); +} + +AdsManager createAdsManager(PlatformAdsManager platformManager) { + InteractiveMediaAdsPlatform.instance = TestInteractiveMediaAdsPlatform( + onCreatePlatformAdsLoader: (PlatformAdsLoaderCreationParams params) { + return TestPlatformAdsLoader(params, + onContentComplete: () async {}, + onRequestAds: (AdsRequest request) async {}); + }, + onCreatePlatformAdsManagerDelegate: + (PlatformAdsManagerDelegateCreationParams params) { + throw UnimplementedError(); + }, + onCreatePlatformAdDisplayContainer: + (PlatformAdDisplayContainerCreationParams params) { + throw UnimplementedError(); + }, + ); + + late final AdsManager manager; + + final AdsLoader loader = AdsLoader( + container: AdDisplayContainer.fromPlatform( + platform: TestPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + ), + onBuild: (_) => Container(), + ), + ), + onAdsLoaded: (OnAdsLoadedData data) { + manager = data.manager; + }, + onAdsLoadError: (_) {}, + ); + + loader.platform.params.onAdsLoaded(PlatformOnAdsLoadedData( + manager: platformManager, + )); + + return manager; +} diff --git a/packages/interactive_media_ads/test/test_stubs.dart b/packages/interactive_media_ads/test/test_stubs.dart new file mode 100644 index 000000000000..fd59fb3073f1 --- /dev/null +++ b/packages/interactive_media_ads/test/test_stubs.dart @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; + +final class TestInteractiveMediaAdsPlatform + extends InteractiveMediaAdsPlatform { + TestInteractiveMediaAdsPlatform({ + required this.onCreatePlatformAdsLoader, + required this.onCreatePlatformAdsManagerDelegate, + required this.onCreatePlatformAdDisplayContainer, + }); + + PlatformAdsLoader Function(PlatformAdsLoaderCreationParams params) + onCreatePlatformAdsLoader; + + PlatformAdsManagerDelegate Function( + PlatformAdsManagerDelegateCreationParams params, + ) onCreatePlatformAdsManagerDelegate; + + PlatformAdDisplayContainer Function( + PlatformAdDisplayContainerCreationParams params, + ) onCreatePlatformAdDisplayContainer; + + @override + PlatformAdsLoader createPlatformAdsLoader( + PlatformAdsLoaderCreationParams params, + ) { + return onCreatePlatformAdsLoader(params); + } + + @override + PlatformAdsManagerDelegate createPlatformAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams params, + ) { + return onCreatePlatformAdsManagerDelegate(params); + } + + @override + PlatformAdDisplayContainer createPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams params, + ) { + return onCreatePlatformAdDisplayContainer(params); + } +} + +final class TestPlatformAdDisplayContainer extends PlatformAdDisplayContainer { + TestPlatformAdDisplayContainer( + super.params, { + required this.onBuild, + }) : super.implementation(); + + Widget Function(BuildContext context) onBuild; + + @override + Widget build(BuildContext context) { + return onBuild.call(context); + } +} + +final class TestPlatformAdsLoader extends PlatformAdsLoader { + TestPlatformAdsLoader( + super.params, { + required this.onContentComplete, + required this.onRequestAds, + }) : super.implementation(); + + Future Function() onContentComplete; + + Future Function(AdsRequest request) onRequestAds; + + @override + Future contentComplete() async { + return onContentComplete(); + } + + @override + Future requestAds(AdsRequest request) async { + return onRequestAds(request); + } +} + +final class TestPlatformAdsManagerDelegate extends PlatformAdsManagerDelegate { + TestPlatformAdsManagerDelegate(super.params) : super.implementation(); +} + +class TestAdsManager extends PlatformAdsManager { + TestAdsManager({ + this.onInit, + this.onSetAdsManagerDelegate, + this.onStart, + this.onDestroy, + }); + + Future Function(AdsManagerInitParams params)? onInit; + + Future Function(PlatformAdsManagerDelegate delegate)? + onSetAdsManagerDelegate; + + Future Function(AdsManagerStartParams params)? onStart; + + Future Function()? onDestroy; + + @override + Future init(AdsManagerInitParams params) async { + return onInit?.call(params); + } + + @override + Future setAdsManagerDelegate( + PlatformAdsManagerDelegate delegate, + ) async { + return onSetAdsManagerDelegate?.call(delegate); + } + + @override + Future start(AdsManagerStartParams params) async { + return onStart?.call(params); + } + + @override + Future destroy() async { + return onDestroy?.call(); + } +} diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index 475c3f5a3417..b7b2c263da5f 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,9 +1,10 @@ -## NEXT +## 2.2.0 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Switches endorsed iOS implementation to `local_auth_darwin`. + * Clients directly importing `local_auth_ios` for auth strings should switch + dependencies and imports to `local_auth_darwin`. No other change is necessary. * Updates support matrix in README to indicate that iOS 11 is no longer supported. -* Clients on versions of Flutter that still support iOS 11 can continue to use this - package with iOS 11, but will not receive any further updates to the iOS implementation. +* Updates minimum supported SDK version to Flutter 3.16.6. ## 2.1.8 diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 8c86785d21f3..78c0b2038a19 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -136,7 +136,7 @@ instance, to customize Android and iOS: ```dart import 'package:local_auth_android/local_auth_android.dart'; -import 'package:local_auth_ios/local_auth_ios.dart'; +import 'package:local_auth_darwin/local_auth_darwin.dart'; // ··· final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', @@ -281,7 +281,7 @@ the Android theme directly in `android/app/src/main/AndroidManifest.xml`: diff --git a/packages/local_auth/local_auth/example/lib/readme_excerpts.dart b/packages/local_auth/local_auth/example/lib/readme_excerpts.dart index 8ae4a3c34178..b0ee0e9daf0d 100644 --- a/packages/local_auth/local_auth/example/lib/readme_excerpts.dart +++ b/packages/local_auth/local_auth/example/lib/readme_excerpts.dart @@ -20,7 +20,7 @@ import 'package:local_auth/local_auth.dart'; // #docregion CustomMessages import 'package:local_auth_android/local_auth_android.dart'; -import 'package:local_auth_ios/local_auth_ios.dart'; +import 'package:local_auth_darwin/local_auth_darwin.dart'; // #enddocregion CustomMessages void main() { diff --git a/packages/local_auth/local_auth/example/pubspec.yaml b/packages/local_auth/local_auth/example/pubspec.yaml index ea71356935a9..73265f90cd33 100644 --- a/packages/local_auth/local_auth/example/pubspec.yaml +++ b/packages/local_auth/local_auth/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the local_auth plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.3 + flutter: ">=3.16.6" dependencies: flutter: @@ -17,7 +17,7 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ local_auth_android: ^1.0.0 - local_auth_ios: ^1.0.1 + local_auth_darwin: ^1.2.1 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/local_auth/local_auth/example/windows/flutter/CMakeLists.txt b/packages/local_auth/local_auth/example/windows/flutter/CMakeLists.txt index d83cc95319b6..713648b939df 100644 --- a/packages/local_auth/local_auth/example/windows/flutter/CMakeLists.txt +++ b/packages/local_auth/local_auth/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/local_auth/local_auth/example/windows/runner/Runner.rc b/packages/local_auth/local_auth/example/windows/runner/Runner.rc index 7e35b9f56a22..7f035cd3f7ed 100644 --- a/packages/local_auth/local_auth/example/windows/runner/Runner.rc +++ b/packages/local_auth/local_auth/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/local_auth/local_auth/lib/src/local_auth.dart b/packages/local_auth/local_auth/lib/src/local_auth.dart index e369f67187a5..9046743dd5d3 100644 --- a/packages/local_auth/local_auth/lib/src/local_auth.dart +++ b/packages/local_auth/local_auth/lib/src/local_auth.dart @@ -12,7 +12,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:local_auth_android/local_auth_android.dart'; -import 'package:local_auth_ios/local_auth_ios.dart'; +import 'package:local_auth_darwin/local_auth_darwin.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index 6573656a4496..f36bc4049def 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 2.1.8 +version: 2.2.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.3 + flutter: ">=3.16.6" flutter: plugin: @@ -15,7 +15,7 @@ flutter: android: default_package: local_auth_android ios: - default_package: local_auth_ios + default_package: local_auth_darwin windows: default_package: local_auth_windows @@ -23,7 +23,7 @@ dependencies: flutter: sdk: flutter local_auth_android: ^1.0.0 - local_auth_ios: ^1.0.1 + local_auth_darwin: ^1.2.1 local_auth_platform_interface: ^1.0.1 local_auth_windows: ^1.0.0 diff --git a/packages/local_auth/local_auth/test/local_auth_test.dart b/packages/local_auth/local_auth/test/local_auth_test.dart index 00196a8b875e..aa9c5cf5e6b6 100644 --- a/packages/local_auth/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/local_auth/test/local_auth_test.dart @@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; -import 'package:local_auth_ios/local_auth_ios.dart'; +import 'package:local_auth_darwin/local_auth_darwin.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m b/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m index 6b9ef39f6e2d..e57adcdff30d 100644 --- a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m +++ b/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m @@ -8,9 +8,7 @@ typedef void (^FLADAuthCompletion)(FLADAuthResultDetails *_Nullable, FlutterError *_Nullable); -/** - * A default context factory that wraps standard LAContext allocation. - */ +/// A default context factory that wraps standard LAContext allocation. @interface FLADefaultAuthContextFactory : NSObject @end @@ -22,9 +20,7 @@ - (LAContext *)createAuthContext { #pragma mark - -/** - * A data container for sticky auth state. - */ +/// A data container for sticky auth state. @interface FLAStickyAuthState : NSObject @property(nonatomic, strong, nonnull) FLADAuthOptions *options; @property(nonatomic, strong, nonnull) FLADAuthStrings *strings; diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h b/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h index 79ac48ec8cbc..eb12b29fae3b 100644 --- a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h +++ b/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h @@ -5,17 +5,13 @@ #import #import -/** - * Protocol for a source of LAContext instances. Used to allow context injection in unit tests. - */ +/// Protocol for a source of LAContext instances. Used to allow context injection in unit tests. @protocol FLADAuthContextFactory - (LAContext *)createAuthContext; @end @interface FLALocalAuthPlugin () -/** - * Returns an instance that uses the given factory to create LAContexts. - */ +/// Returns an instance that uses the given factory to create LAContexts. - (instancetype)initWithContextFactory:(NSObject *)factory NS_DESIGNATED_INITIALIZER; @end diff --git a/packages/local_auth/local_auth_windows/example/windows/flutter/CMakeLists.txt b/packages/local_auth/local_auth_windows/example/windows/flutter/CMakeLists.txt index b2e4bd8d658b..4f2af69bbb09 100644 --- a/packages/local_auth/local_auth_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/local_auth/local_auth_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/local_auth/local_auth_windows/example/windows/runner/Runner.rc b/packages/local_auth/local_auth_windows/example/windows/runner/Runner.rc index 5fdea291cf19..0f5c0857111f 100644 --- a/packages/local_auth/local_auth_windows/example/windows/runner/Runner.rc +++ b/packages/local_auth/local_auth_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/metrics_center/CHANGELOG.md b/packages/metrics_center/CHANGELOG.md index 0d589218f8ab..a661251dc1d2 100644 --- a/packages/metrics_center/CHANGELOG.md +++ b/packages/metrics_center/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 1.0.13 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates dependency on `package:googleapis` to `^12.0.0`. ## 1.0.12 diff --git a/packages/metrics_center/pubspec.yaml b/packages/metrics_center/pubspec.yaml index efc8ffcd5d92..581be39e283c 100644 --- a/packages/metrics_center/pubspec.yaml +++ b/packages/metrics_center/pubspec.yaml @@ -1,5 +1,5 @@ name: metrics_center -version: 1.0.12 +version: 1.0.13 description: Support multiple performance metrics sources/formats and destinations. repository: https://github.com/flutter/packages/tree/main/packages/metrics_center @@ -12,7 +12,7 @@ dependencies: _discoveryapis_commons: ^1.0.0 crypto: ^3.0.1 gcloud: ^0.8.2 - googleapis: ^3.0.0 + googleapis: ^12.0.0 googleapis_auth: ^1.1.0 http: ">=0.13.0 <2.0.0" diff --git a/packages/metrics_center/test/gcs_lock_test.mocks.dart b/packages/metrics_center/test/gcs_lock_test.mocks.dart index f28daf64f6e5..d013ac823f25 100644 --- a/packages/metrics_center/test/gcs_lock_test.mocks.dart +++ b/packages/metrics_center/test/gcs_lock_test.mocks.dart @@ -60,9 +60,20 @@ class _FakeStreamedResponse_2 extends _i1.SmartFake ); } -class _FakeBucketAccessControlsResource_3 extends _i1.SmartFake +class _FakeAnywhereCacheResource_3 extends _i1.SmartFake + implements _i4.AnywhereCacheResource { + _FakeAnywhereCacheResource_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBucketAccessControlsResource_4 extends _i1.SmartFake implements _i4.BucketAccessControlsResource { - _FakeBucketAccessControlsResource_3( + _FakeBucketAccessControlsResource_4( Object parent, Invocation parentInvocation, ) : super( @@ -71,9 +82,9 @@ class _FakeBucketAccessControlsResource_3 extends _i1.SmartFake ); } -class _FakeBucketsResource_4 extends _i1.SmartFake +class _FakeBucketsResource_5 extends _i1.SmartFake implements _i4.BucketsResource { - _FakeBucketsResource_4( + _FakeBucketsResource_5( Object parent, Invocation parentInvocation, ) : super( @@ -82,9 +93,9 @@ class _FakeBucketsResource_4 extends _i1.SmartFake ); } -class _FakeChannelsResource_5 extends _i1.SmartFake +class _FakeChannelsResource_6 extends _i1.SmartFake implements _i4.ChannelsResource { - _FakeChannelsResource_5( + _FakeChannelsResource_6( Object parent, Invocation parentInvocation, ) : super( @@ -93,9 +104,20 @@ class _FakeChannelsResource_5 extends _i1.SmartFake ); } -class _FakeDefaultObjectAccessControlsResource_6 extends _i1.SmartFake +class _FakeDefaultObjectAccessControlsResource_7 extends _i1.SmartFake implements _i4.DefaultObjectAccessControlsResource { - _FakeDefaultObjectAccessControlsResource_6( + _FakeDefaultObjectAccessControlsResource_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeManagedFoldersResource_8 extends _i1.SmartFake + implements _i4.ManagedFoldersResource { + _FakeManagedFoldersResource_8( Object parent, Invocation parentInvocation, ) : super( @@ -104,9 +126,9 @@ class _FakeDefaultObjectAccessControlsResource_6 extends _i1.SmartFake ); } -class _FakeNotificationsResource_7 extends _i1.SmartFake +class _FakeNotificationsResource_9 extends _i1.SmartFake implements _i4.NotificationsResource { - _FakeNotificationsResource_7( + _FakeNotificationsResource_9( Object parent, Invocation parentInvocation, ) : super( @@ -115,9 +137,9 @@ class _FakeNotificationsResource_7 extends _i1.SmartFake ); } -class _FakeObjectAccessControlsResource_8 extends _i1.SmartFake +class _FakeObjectAccessControlsResource_10 extends _i1.SmartFake implements _i4.ObjectAccessControlsResource { - _FakeObjectAccessControlsResource_8( + _FakeObjectAccessControlsResource_10( Object parent, Invocation parentInvocation, ) : super( @@ -126,9 +148,9 @@ class _FakeObjectAccessControlsResource_8 extends _i1.SmartFake ); } -class _FakeObjectsResource_9 extends _i1.SmartFake +class _FakeObjectsResource_11 extends _i1.SmartFake implements _i4.ObjectsResource { - _FakeObjectsResource_9( + _FakeObjectsResource_11( Object parent, Invocation parentInvocation, ) : super( @@ -137,9 +159,20 @@ class _FakeObjectsResource_9 extends _i1.SmartFake ); } -class _FakeProjectsResource_10 extends _i1.SmartFake +class _FakeOperationsResource_12 extends _i1.SmartFake + implements _i4.OperationsResource { + _FakeOperationsResource_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeProjectsResource_13 extends _i1.SmartFake implements _i4.ProjectsResource { - _FakeProjectsResource_10( + _FakeProjectsResource_13( Object parent, Invocation parentInvocation, ) : super( @@ -148,8 +181,9 @@ class _FakeProjectsResource_10 extends _i1.SmartFake ); } -class _FakeObject_11 extends _i1.SmartFake implements _i4.Object { - _FakeObject_11( +class _FakeGoogleLongrunningOperation_14 extends _i1.SmartFake + implements _i4.GoogleLongrunningOperation { + _FakeGoogleLongrunningOperation_14( Object parent, Invocation parentInvocation, ) : super( @@ -158,8 +192,8 @@ class _FakeObject_11 extends _i1.SmartFake implements _i4.Object { ); } -class _FakeObject_12 extends _i1.SmartFake implements Object { - _FakeObject_12( +class _FakeObject_15 extends _i1.SmartFake implements _i4.Object { + _FakeObject_15( Object parent, Invocation parentInvocation, ) : super( @@ -168,8 +202,8 @@ class _FakeObject_12 extends _i1.SmartFake implements Object { ); } -class _FakePolicy_13 extends _i1.SmartFake implements _i4.Policy { - _FakePolicy_13( +class _FakeObject_16 extends _i1.SmartFake implements Object { + _FakeObject_16( Object parent, Invocation parentInvocation, ) : super( @@ -178,8 +212,8 @@ class _FakePolicy_13 extends _i1.SmartFake implements _i4.Policy { ); } -class _FakeObjects_14 extends _i1.SmartFake implements _i4.Objects { - _FakeObjects_14( +class _FakePolicy_17 extends _i1.SmartFake implements _i4.Policy { + _FakePolicy_17( Object parent, Invocation parentInvocation, ) : super( @@ -188,9 +222,19 @@ class _FakeObjects_14 extends _i1.SmartFake implements _i4.Objects { ); } -class _FakeRewriteResponse_15 extends _i1.SmartFake +class _FakeObjects_18 extends _i1.SmartFake implements _i4.Objects { + _FakeObjects_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRewriteResponse_19 extends _i1.SmartFake implements _i4.RewriteResponse { - _FakeRewriteResponse_15( + _FakeRewriteResponse_19( Object parent, Invocation parentInvocation, ) : super( @@ -199,9 +243,9 @@ class _FakeRewriteResponse_15 extends _i1.SmartFake ); } -class _FakeTestIamPermissionsResponse_16 extends _i1.SmartFake +class _FakeTestIamPermissionsResponse_20 extends _i1.SmartFake implements _i4.TestIamPermissionsResponse { - _FakeTestIamPermissionsResponse_16( + _FakeTestIamPermissionsResponse_20( Object parent, Invocation parentInvocation, ) : super( @@ -210,8 +254,8 @@ class _FakeTestIamPermissionsResponse_16 extends _i1.SmartFake ); } -class _FakeChannel_17 extends _i1.SmartFake implements _i4.Channel { - _FakeChannel_17( +class _FakeChannel_21 extends _i1.SmartFake implements _i4.Channel { + _FakeChannel_21( Object parent, Invocation parentInvocation, ) : super( @@ -473,11 +517,20 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { _i1.throwOnMissingStub(this); } + @override + _i4.AnywhereCacheResource get anywhereCache => (super.noSuchMethod( + Invocation.getter(#anywhereCache), + returnValue: _FakeAnywhereCacheResource_3( + this, + Invocation.getter(#anywhereCache), + ), + ) as _i4.AnywhereCacheResource); + @override _i4.BucketAccessControlsResource get bucketAccessControls => (super.noSuchMethod( Invocation.getter(#bucketAccessControls), - returnValue: _FakeBucketAccessControlsResource_3( + returnValue: _FakeBucketAccessControlsResource_4( this, Invocation.getter(#bucketAccessControls), ), @@ -486,7 +539,7 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { @override _i4.BucketsResource get buckets => (super.noSuchMethod( Invocation.getter(#buckets), - returnValue: _FakeBucketsResource_4( + returnValue: _FakeBucketsResource_5( this, Invocation.getter(#buckets), ), @@ -495,7 +548,7 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { @override _i4.ChannelsResource get channels => (super.noSuchMethod( Invocation.getter(#channels), - returnValue: _FakeChannelsResource_5( + returnValue: _FakeChannelsResource_6( this, Invocation.getter(#channels), ), @@ -505,16 +558,25 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { _i4.DefaultObjectAccessControlsResource get defaultObjectAccessControls => (super.noSuchMethod( Invocation.getter(#defaultObjectAccessControls), - returnValue: _FakeDefaultObjectAccessControlsResource_6( + returnValue: _FakeDefaultObjectAccessControlsResource_7( this, Invocation.getter(#defaultObjectAccessControls), ), ) as _i4.DefaultObjectAccessControlsResource); + @override + _i4.ManagedFoldersResource get managedFolders => (super.noSuchMethod( + Invocation.getter(#managedFolders), + returnValue: _FakeManagedFoldersResource_8( + this, + Invocation.getter(#managedFolders), + ), + ) as _i4.ManagedFoldersResource); + @override _i4.NotificationsResource get notifications => (super.noSuchMethod( Invocation.getter(#notifications), - returnValue: _FakeNotificationsResource_7( + returnValue: _FakeNotificationsResource_9( this, Invocation.getter(#notifications), ), @@ -524,7 +586,7 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { _i4.ObjectAccessControlsResource get objectAccessControls => (super.noSuchMethod( Invocation.getter(#objectAccessControls), - returnValue: _FakeObjectAccessControlsResource_8( + returnValue: _FakeObjectAccessControlsResource_10( this, Invocation.getter(#objectAccessControls), ), @@ -533,16 +595,25 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { @override _i4.ObjectsResource get objects => (super.noSuchMethod( Invocation.getter(#objects), - returnValue: _FakeObjectsResource_9( + returnValue: _FakeObjectsResource_11( this, Invocation.getter(#objects), ), ) as _i4.ObjectsResource); + @override + _i4.OperationsResource get operations => (super.noSuchMethod( + Invocation.getter(#operations), + returnValue: _FakeOperationsResource_12( + this, + Invocation.getter(#operations), + ), + ) as _i4.OperationsResource); + @override _i4.ProjectsResource get projects => (super.noSuchMethod( Invocation.getter(#projects), - returnValue: _FakeProjectsResource_10( + returnValue: _FakeProjectsResource_13( this, Invocation.getter(#projects), ), @@ -553,6 +624,48 @@ class MockStorageApi extends _i1.Mock implements _i4.StorageApi { /// /// See the documentation for Mockito's code generation for more information. class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { + @override + _i6.Future<_i4.GoogleLongrunningOperation> bulkRestore( + _i4.BulkRestoreObjectsRequest? request, + String? bucket, { + String? $fields, + }) => + (super.noSuchMethod( + Invocation.method( + #bulkRestore, + [ + request, + bucket, + ], + {#$fields: $fields}, + ), + returnValue: _i6.Future<_i4.GoogleLongrunningOperation>.value( + _FakeGoogleLongrunningOperation_14( + this, + Invocation.method( + #bulkRestore, + [ + request, + bucket, + ], + {#$fields: $fields}, + ), + )), + returnValueForMissingStub: + _i6.Future<_i4.GoogleLongrunningOperation>.value( + _FakeGoogleLongrunningOperation_14( + this, + Invocation.method( + #bulkRestore, + [ + request, + bucket, + ], + {#$fields: $fields}, + ), + )), + ) as _i6.Future<_i4.GoogleLongrunningOperation>); + @override _i6.Future<_i4.Object> compose( _i4.ComposeRequest? request, @@ -562,7 +675,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifGenerationMatch, String? ifMetagenerationMatch, String? kmsKeyName, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -579,12 +691,11 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationMatch: ifGenerationMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #kmsKeyName: kmsKeyName, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #compose, @@ -598,13 +709,12 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationMatch: ifGenerationMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #kmsKeyName: kmsKeyName, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #compose, @@ -618,7 +728,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationMatch: ifGenerationMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #kmsKeyName: kmsKeyName, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -644,7 +753,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifSourceMetagenerationMatch, String? ifSourceMetagenerationNotMatch, String? projection, - String? provisionalUserProject, String? sourceGeneration, String? userProject, String? $fields, @@ -671,13 +779,12 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationMatch: ifSourceMetagenerationMatch, #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, #sourceGeneration: sourceGeneration, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #copy, @@ -700,14 +807,13 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationMatch: ifSourceMetagenerationMatch, #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, #sourceGeneration: sourceGeneration, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #copy, @@ -730,7 +836,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationMatch: ifSourceMetagenerationMatch, #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, #sourceGeneration: sourceGeneration, #userProject: userProject, #$fields: $fields, @@ -748,7 +853,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifGenerationNotMatch, String? ifMetagenerationMatch, String? ifMetagenerationNotMatch, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -765,7 +869,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -784,7 +887,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifMetagenerationMatch, String? ifMetagenerationNotMatch, String? projection, - String? provisionalUserProject, + bool? softDeleted, String? userProject, String? $fields, _i10.DownloadOptions? downloadOptions = _i10.DownloadOptions.metadata, @@ -803,13 +906,13 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #userProject: userProject, #$fields: $fields, #downloadOptions: downloadOptions, }, ), - returnValue: _i6.Future.value(_FakeObject_12( + returnValue: _i6.Future.value(_FakeObject_16( this, Invocation.method( #get, @@ -824,14 +927,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #userProject: userProject, #$fields: $fields, #downloadOptions: downloadOptions, }, ), )), - returnValueForMissingStub: _i6.Future.value(_FakeObject_12( + returnValueForMissingStub: _i6.Future.value(_FakeObject_16( this, Invocation.method( #get, @@ -846,7 +949,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #userProject: userProject, #$fields: $fields, #downloadOptions: downloadOptions, @@ -860,7 +963,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? bucket, String? object, { String? generation, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -873,12 +975,11 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Policy>.value(_FakePolicy_13( + returnValue: _i6.Future<_i4.Policy>.value(_FakePolicy_17( this, Invocation.method( #getIamPolicy, @@ -888,13 +989,12 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Policy>.value(_FakePolicy_13( + returnValueForMissingStub: _i6.Future<_i4.Policy>.value(_FakePolicy_17( this, Invocation.method( #getIamPolicy, @@ -904,7 +1004,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -925,7 +1024,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? name, String? predefinedAcl, String? projection, - String? provisionalUserProject, String? userProject, String? $fields, _i10.UploadOptions? uploadOptions = _i10.UploadOptions.defaultOptions, @@ -948,14 +1046,13 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #name: name, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, #uploadOptions: uploadOptions, #uploadMedia: uploadMedia, }, ), - returnValue: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #insert, @@ -973,7 +1070,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #name: name, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, #uploadOptions: uploadOptions, @@ -981,7 +1077,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #insert, @@ -999,7 +1095,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #name: name, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, #uploadOptions: uploadOptions, @@ -1014,12 +1109,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? bucket, { String? delimiter, String? endOffset, + bool? includeFoldersAsPrefixes, bool? includeTrailingDelimiter, + String? matchGlob, int? maxResults, String? pageToken, String? prefix, String? projection, - String? provisionalUserProject, + bool? softDeleted, String? startOffset, String? userProject, bool? versions, @@ -1032,19 +1129,21 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { { #delimiter: delimiter, #endOffset: endOffset, + #includeFoldersAsPrefixes: includeFoldersAsPrefixes, #includeTrailingDelimiter: includeTrailingDelimiter, + #matchGlob: matchGlob, #maxResults: maxResults, #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #startOffset: startOffset, #userProject: userProject, #versions: versions, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Objects>.value(_FakeObjects_14( + returnValue: _i6.Future<_i4.Objects>.value(_FakeObjects_18( this, Invocation.method( #list, @@ -1052,12 +1151,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { { #delimiter: delimiter, #endOffset: endOffset, + #includeFoldersAsPrefixes: includeFoldersAsPrefixes, #includeTrailingDelimiter: includeTrailingDelimiter, + #matchGlob: matchGlob, #maxResults: maxResults, #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #startOffset: startOffset, #userProject: userProject, #versions: versions, @@ -1066,7 +1167,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ), )), returnValueForMissingStub: - _i6.Future<_i4.Objects>.value(_FakeObjects_14( + _i6.Future<_i4.Objects>.value(_FakeObjects_18( this, Invocation.method( #list, @@ -1074,12 +1175,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { { #delimiter: delimiter, #endOffset: endOffset, + #includeFoldersAsPrefixes: includeFoldersAsPrefixes, #includeTrailingDelimiter: includeTrailingDelimiter, + #matchGlob: matchGlob, #maxResults: maxResults, #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #softDeleted: softDeleted, #startOffset: startOffset, #userProject: userProject, #versions: versions, @@ -1099,9 +1202,9 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifGenerationNotMatch, String? ifMetagenerationMatch, String? ifMetagenerationNotMatch, + bool? overrideUnlockedRetention, String? predefinedAcl, String? projection, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -1119,14 +1222,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #patch, @@ -1141,15 +1244,15 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #patch, @@ -1164,9 +1267,90 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, + #userProject: userProject, + #$fields: $fields, + }, + ), + )), + ) as _i6.Future<_i4.Object>); + + @override + _i6.Future<_i4.Object> restore( + _i4.Object? request, + String? bucket, + String? object, + String? generation, { + bool? copySourceAcl, + String? ifGenerationMatch, + String? ifGenerationNotMatch, + String? ifMetagenerationMatch, + String? ifMetagenerationNotMatch, + String? projection, + String? userProject, + String? $fields, + }) => + (super.noSuchMethod( + Invocation.method( + #restore, + [ + request, + bucket, + object, + generation, + ], + { + #copySourceAcl: copySourceAcl, + #ifGenerationMatch: ifGenerationMatch, + #ifGenerationNotMatch: ifGenerationNotMatch, + #ifMetagenerationMatch: ifMetagenerationMatch, + #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #projection: projection, + #userProject: userProject, + #$fields: $fields, + }, + ), + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( + this, + Invocation.method( + #restore, + [ + request, + bucket, + object, + generation, + ], + { + #copySourceAcl: copySourceAcl, + #ifGenerationMatch: ifGenerationMatch, + #ifGenerationNotMatch: ifGenerationNotMatch, + #ifMetagenerationMatch: ifMetagenerationMatch, + #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #projection: projection, + #userProject: userProject, + #$fields: $fields, + }, + ), + )), + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( + this, + Invocation.method( + #restore, + [ + request, + bucket, + object, + generation, + ], + { + #copySourceAcl: copySourceAcl, + #ifGenerationMatch: ifGenerationMatch, + #ifGenerationNotMatch: ifGenerationNotMatch, + #ifMetagenerationMatch: ifMetagenerationMatch, + #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #projection: projection, #userProject: userProject, #$fields: $fields, }, @@ -1193,7 +1377,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifSourceMetagenerationNotMatch, String? maxBytesRewrittenPerCall, String? projection, - String? provisionalUserProject, String? rewriteToken, String? sourceGeneration, String? userProject, @@ -1222,7 +1405,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #maxBytesRewrittenPerCall: maxBytesRewrittenPerCall, #projection: projection, - #provisionalUserProject: provisionalUserProject, #rewriteToken: rewriteToken, #sourceGeneration: sourceGeneration, #userProject: userProject, @@ -1230,7 +1412,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { }, ), returnValue: - _i6.Future<_i4.RewriteResponse>.value(_FakeRewriteResponse_15( + _i6.Future<_i4.RewriteResponse>.value(_FakeRewriteResponse_19( this, Invocation.method( #rewrite, @@ -1254,7 +1436,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #maxBytesRewrittenPerCall: maxBytesRewrittenPerCall, #projection: projection, - #provisionalUserProject: provisionalUserProject, #rewriteToken: rewriteToken, #sourceGeneration: sourceGeneration, #userProject: userProject, @@ -1263,7 +1444,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ), )), returnValueForMissingStub: - _i6.Future<_i4.RewriteResponse>.value(_FakeRewriteResponse_15( + _i6.Future<_i4.RewriteResponse>.value(_FakeRewriteResponse_19( this, Invocation.method( #rewrite, @@ -1287,7 +1468,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifSourceMetagenerationNotMatch: ifSourceMetagenerationNotMatch, #maxBytesRewrittenPerCall: maxBytesRewrittenPerCall, #projection: projection, - #provisionalUserProject: provisionalUserProject, #rewriteToken: rewriteToken, #sourceGeneration: sourceGeneration, #userProject: userProject, @@ -1303,7 +1483,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? bucket, String? object, { String? generation, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -1317,12 +1496,11 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Policy>.value(_FakePolicy_13( + returnValue: _i6.Future<_i4.Policy>.value(_FakePolicy_17( this, Invocation.method( #setIamPolicy, @@ -1333,13 +1511,12 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Policy>.value(_FakePolicy_13( + returnValueForMissingStub: _i6.Future<_i4.Policy>.value(_FakePolicy_17( this, Invocation.method( #setIamPolicy, @@ -1350,7 +1527,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -1364,7 +1540,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? object, List? permissions, { String? generation, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -1378,13 +1553,12 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), returnValue: _i6.Future<_i4.TestIamPermissionsResponse>.value( - _FakeTestIamPermissionsResponse_16( + _FakeTestIamPermissionsResponse_20( this, Invocation.method( #testIamPermissions, @@ -1395,7 +1569,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -1403,7 +1576,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { )), returnValueForMissingStub: _i6.Future<_i4.TestIamPermissionsResponse>.value( - _FakeTestIamPermissionsResponse_16( + _FakeTestIamPermissionsResponse_20( this, Invocation.method( #testIamPermissions, @@ -1414,7 +1587,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ], { #generation: generation, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -1432,9 +1604,9 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? ifGenerationNotMatch, String? ifMetagenerationMatch, String? ifMetagenerationNotMatch, + bool? overrideUnlockedRetention, String? predefinedAcl, String? projection, - String? provisionalUserProject, String? userProject, String? $fields, }) => @@ -1452,14 +1624,14 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValue: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #update, @@ -1474,15 +1646,15 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, ), )), - returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_11( + returnValueForMissingStub: _i6.Future<_i4.Object>.value(_FakeObject_15( this, Invocation.method( #update, @@ -1497,9 +1669,9 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #ifGenerationNotMatch: ifGenerationNotMatch, #ifMetagenerationMatch: ifMetagenerationMatch, #ifMetagenerationNotMatch: ifMetagenerationNotMatch, + #overrideUnlockedRetention: overrideUnlockedRetention, #predefinedAcl: predefinedAcl, #projection: projection, - #provisionalUserProject: provisionalUserProject, #userProject: userProject, #$fields: $fields, }, @@ -1518,7 +1690,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { String? pageToken, String? prefix, String? projection, - String? provisionalUserProject, String? startOffset, String? userProject, bool? versions, @@ -1539,14 +1710,13 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, #startOffset: startOffset, #userProject: userProject, #versions: versions, #$fields: $fields, }, ), - returnValue: _i6.Future<_i4.Channel>.value(_FakeChannel_17( + returnValue: _i6.Future<_i4.Channel>.value(_FakeChannel_21( this, Invocation.method( #watchAll, @@ -1562,7 +1732,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, #startOffset: startOffset, #userProject: userProject, #versions: versions, @@ -1571,7 +1740,7 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { ), )), returnValueForMissingStub: - _i6.Future<_i4.Channel>.value(_FakeChannel_17( + _i6.Future<_i4.Channel>.value(_FakeChannel_21( this, Invocation.method( #watchAll, @@ -1587,7 +1756,6 @@ class MockObjectsResource extends _i1.Mock implements _i4.ObjectsResource { #pageToken: pageToken, #prefix: prefix, #projection: projection, - #provisionalUserProject: provisionalUserProject, #startOffset: startOffset, #userProject: userProject, #versions: versions, diff --git a/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt b/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt index c7a8c7607d81..c25ffc272ada 100644 --- a/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt +++ b/packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,6 +96,7 @@ add_custom_command( ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ + VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" diff --git a/packages/path_provider/path_provider/example/windows/runner/Runner.rc b/packages/path_provider/path_provider/example/windows/runner/Runner.rc index dbda44723259..4d7a2dbcdc20 100644 --- a/packages/path_provider/path_provider/example/windows/runner/Runner.rc +++ b/packages/path_provider/path_provider/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt b/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt index 744f08a9389b..0a9177722722 100644 --- a/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -90,7 +95,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc b/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc index 944329afc03a..41d1b5cf736b 100644 --- a/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc +++ b/packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 967426ce7c2a..be39b24414c6 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,5 +1,27 @@ -## NEXT +## 17.3.0 +* [swift] Adds `@SwiftClass` annotation to allow choice between `struct` and `class` for data classes. +* [cpp] Adds support for recursive data class definitions. + +## 17.2.0 + +* [dart] Adds implementation for `@ProxyApi`. + +## 17.1.3 + +* [objc] Fixes double prefixes added to enum names. + +## 17.1.2 + +* [swift] Separates message call code generation into separate methods. + +## 17.1.1 + +* Removes heap allocation in generated C++ code. + +## 17.1.0 + +* [kotlin] Adds `includeErrorClass` to `KotlinOptions`. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 17.0.0 diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index 213e650c6324..8e1edaa30df2 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -27,6 +27,11 @@ Custom classes, nested datatypes, and enums are also supported. Nullable enums in Objective-C generated code will be wrapped in a class to allow for nullability. +By default, custom classes in Swift are defined as structs. +Structs don't support some features - recursive data, or Objective-C interop. +Use the @SwiftClass annotation when defining the class to generate the data +as a Swift class instead. + ### Synchronous and Asynchronous methods While all calls across platform channel APIs (such as pigeon methods) are asynchronous, diff --git a/packages/pigeon/example/app/README.md b/packages/pigeon/example/app/README.md index 9ac1a473e203..9bc223a17ab0 100644 --- a/packages/pigeon/example/app/README.md +++ b/packages/pigeon/example/app/README.md @@ -5,5 +5,6 @@ application, rather than in a plugin. To update the generated code, run: ```sh -dart run pigeon --input pigeons/messages.dart +cd ../.. +dart tool/generate.dart ``` diff --git a/packages/pigeon/example/app/windows/runner/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp index 4541b9d4f369..97d11da0bdf8 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.cpp +++ b/packages/pigeon/example/app/windows/runner/messages.g.cpp @@ -141,13 +141,12 @@ const flutter::StandardMessageCodec& ExampleHostApi::GetCodec() { void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, ExampleHostApi* api) { { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi." - "getHostLanguage", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_example_package." + "ExampleHostApi.getHostLanguage", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -164,16 +163,16 @@ void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -203,16 +202,16 @@ void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -239,7 +238,7 @@ void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } } @@ -273,12 +272,11 @@ void MessageFlutterApi::FlutterMethod( const std::string channel_name = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi." "flutterMethod"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_string_arg ? EncodableValue(*a_string_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 0c48faa45456..08eef8cc6c7c 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -177,6 +177,133 @@ class AstProxyApi extends Api { (ApiField field) => !field.isAttached, ); + /// A list of AstProxyApis where each `extends` the API that follows it. + /// + /// Returns an empty list if this api does not extend a ProxyApi. + /// + /// This method assumes the super classes of each ProxyApi doesn't create a + /// loop. Throws a [ArgumentError] if a loop is found. + /// + /// This method also assumes that all super classes are ProxyApis. Otherwise, + /// throws an [ArgumentError]. + Iterable allSuperClasses() { + final List superClassChain = []; + + if (superClass != null && !superClass!.isProxyApi) { + throw ArgumentError( + 'Could not find a ProxyApi for super class: ${superClass!.baseName}', + ); + } + + AstProxyApi? currentProxyApi = superClass?.associatedProxyApi; + while (currentProxyApi != null) { + if (superClassChain.contains(currentProxyApi)) { + throw ArgumentError( + 'Loop found when processing super classes for a ProxyApi: ' + '$name, ${superClassChain.map((AstProxyApi api) => api.name)}', + ); + } + + superClassChain.add(currentProxyApi); + + if (currentProxyApi.superClass != null && + !currentProxyApi.superClass!.isProxyApi) { + throw ArgumentError( + 'Could not find a ProxyApi for super class: ' + '${currentProxyApi.superClass!.baseName}', + ); + } + + currentProxyApi = currentProxyApi.superClass?.associatedProxyApi; + } + + return superClassChain; + } + + /// All ProxyApis this API `implements` and all the interfaces those APIs + /// `implements`. + Iterable apisOfInterfaces() => _recursiveFindAllInterfaceApis(); + + /// All methods inherited from interfaces and the interfaces of interfaces. + Iterable flutterMethodsFromInterfaces() sync* { + for (final AstProxyApi proxyApi in apisOfInterfaces()) { + yield* proxyApi.methods; + } + } + + /// A list of Flutter methods inherited from the ProxyApi that this ProxyApi + /// `extends`. + /// + /// This also recursively checks the ProxyApi that the super class `extends` + /// and so on. + /// + /// This also includes methods that super classes inherited from interfaces + /// with `implements`. + Iterable flutterMethodsFromSuperClasses() sync* { + for (final AstProxyApi proxyApi in allSuperClasses().toList().reversed) { + yield* proxyApi.flutterMethods; + } + if (superClass != null) { + final Set interfaceApisFromSuperClasses = + superClass!.associatedProxyApi!._recursiveFindAllInterfaceApis(); + for (final AstProxyApi proxyApi in interfaceApisFromSuperClasses) { + yield* proxyApi.methods; + } + } + } + + /// Whether the api has a method that callbacks to Dart to add a new instance + /// to the InstanceManager. + /// + /// This is possible as long as no callback methods are required to + /// instantiate the class. + bool hasCallbackConstructor() { + return flutterMethods + .followedBy(flutterMethodsFromSuperClasses()) + .followedBy(flutterMethodsFromInterfaces()) + .every((Method method) => !method.isRequired); + } + + // Recursively search for all the interfaces apis from a list of names of + // interfaces. + // + // This method assumes that all interfaces are ProxyApis and an api doesn't + // contains itself as an interface. Otherwise, throws an [ArgumentError]. + Set _recursiveFindAllInterfaceApis([ + Set seenApis = const {}, + ]) { + final Set allInterfaces = {}; + + allInterfaces.addAll( + interfaces.map( + (TypeDeclaration type) { + if (!type.isProxyApi) { + throw ArgumentError( + 'Could not find a valid ProxyApi for an interface: $type', + ); + } else if (seenApis.contains(type.associatedProxyApi)) { + throw ArgumentError( + 'A ProxyApi cannot be a super class of itself: ${type.baseName}', + ); + } + return type.associatedProxyApi!; + }, + ), + ); + + // Adds the current api since it would be invalid for it to be an interface + // of itself. + final Set newSeenApis = {...seenApis, this}; + + for (final AstProxyApi interfaceApi in {...allInterfaces}) { + allInterfaces.addAll( + interfaceApi._recursiveFindAllInterfaceApis(newSeenApis), + ); + } + + return allInterfaces; + } + @override String toString() { return '(ProxyApi name:$name methods:$methods field:$fields ' @@ -512,6 +639,7 @@ class Class extends Node { Class({ required this.name, required this.fields, + this.isSwiftClass = false, this.documentationComments = const [], }); @@ -521,6 +649,12 @@ class Class extends Node { /// All the fields contained in the class. List fields; + /// Determines whether the defined class should be represented as a struct or + /// a class in Swift generation. + /// + /// Defaults to false, which would represent a struct. + bool isSwiftClass; + /// List of documentation comments, separated by line. /// /// Lines should not include the comment marker itself, but should include any diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 419695b4ed2b..5e993137b9f4 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -255,6 +255,35 @@ class CppHeaderGenerator extends StructuredGenerator { _writeClassConstructor(root, indent, classDefinition, orderedFields, 'Constructs an object setting all fields.'); + // If any fields are pointer type, then the class requires a custom + // copy constructor, so declare the rule-of-five group of functions. + if (orderedFields.any((NamedType field) => _isPointerField( + getFieldHostDatatype(field, _baseCppTypeForBuiltinDartType)))) { + final String className = classDefinition.name; + // Add the default destructor, since unique_ptr destroys itself. + _writeFunctionDeclaration(indent, '~$className', defaultImpl: true); + // Declare custom copy/assign to deep-copy the pointer. + _writeFunctionDeclaration(indent, className, + isConstructor: true, + isCopy: true, + parameters: ['const $className& other']); + _writeFunctionDeclaration(indent, 'operator=', + returnType: '$className&', + parameters: ['const $className& other']); + // Re-add the default move operations, since they work fine with + // unique_ptr. + _writeFunctionDeclaration(indent, className, + isConstructor: true, + isCopy: true, + parameters: ['$className&& other'], + defaultImpl: true); + _writeFunctionDeclaration(indent, 'operator=', + returnType: '$className&', + parameters: ['$className&& other'], + defaultImpl: true, + noexcept: true); + } + for (final NamedType field in orderedFields) { addDocumentationComments( indent, field.documentationComments, _docCommentSpec); @@ -313,7 +342,7 @@ class CppHeaderGenerator extends StructuredGenerator { final HostDatatype hostDatatype = getFieldHostDatatype(field, _baseCppTypeForBuiltinDartType); indent.writeln( - '${_valueType(hostDatatype)} ${_makeInstanceVariableName(field)};'); + '${_fieldType(hostDatatype)} ${_makeInstanceVariableName(field)};'); } }); }, nestCount: 0); @@ -693,6 +722,13 @@ class CppSourceGenerator extends StructuredGenerator { // All-field constructor. _writeClassConstructor(root, indent, classDefinition, orderedFields); + // Custom copy/assign to handle pointer fields, if necessary. + if (orderedFields.any((NamedType field) => _isPointerField( + getFieldHostDatatype(field, _baseCppTypeForBuiltinDartType)))) { + _writeCopyConstructor(root, indent, classDefinition, orderedFields); + _writeAssignmentOperator(root, indent, classDefinition, orderedFields); + } + // Getters and setters. for (final NamedType field in orderedFields) { _writeCppSourceClassField( @@ -871,11 +907,9 @@ class CppSourceGenerator extends StructuredGenerator { scope: api.name, returnType: _voidType, parameters: parameters, body: () { - const String channel = 'channel'; indent.writeln( 'const std::string channel_name = "${makeChannelName(api, func, dartPackageName)}";'); - indent.writeln( - 'auto channel = std::make_unique>(binary_messenger_, ' + indent.writeln('BasicMessageChannel<> channel(binary_messenger_, ' 'channel_name, &GetCodec());'); // Convert arguments to EncodableValue versions. @@ -894,7 +928,7 @@ class CppSourceGenerator extends StructuredGenerator { }); } - indent.write('$channel->Send($argumentListVariableName, ' + indent.write('channel.Send($argumentListVariableName, ' // ignore: missing_whitespace_between_adjacent_strings '[channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]' '(const uint8_t* reply, size_t reply_size) '); @@ -972,12 +1006,11 @@ class CppSourceGenerator extends StructuredGenerator { final String channelName = makeChannelName(api, method, dartPackageName); indent.writeScoped('{', '}', () { - indent.writeln( - 'auto channel = std::make_unique>(binary_messenger, ' + indent.writeln('BasicMessageChannel<> channel(binary_messenger, ' '"$channelName", &GetCodec());'); indent.writeScoped('if (api != nullptr) {', '} else {', () { indent.write( - 'channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) '); + 'channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) '); indent.addScoped('{', '});', () { indent.writeScoped('try {', '}', () { final List methodArgument = []; @@ -1054,7 +1087,7 @@ class CppSourceGenerator extends StructuredGenerator { }); }); indent.addScoped(null, '}', () { - indent.writeln('channel->SetMessageHandler(nullptr);'); + indent.writeln('channel.SetMessageHandler(nullptr);'); }); }); } @@ -1172,15 +1205,69 @@ return EncodableValue(EncodableList{ initializers: initializerStrings); } + void _writeCopyConstructor(Root root, Indent indent, Class classDefinition, + Iterable fields) { + final List initializerStrings = fields.map((NamedType param) { + final String fieldName = _makeInstanceVariableName(param); + final HostDatatype hostType = getFieldHostDatatype( + param, + _shortBaseCppTypeForBuiltinDartType, + ); + return '$fieldName(${_fieldValueExpression(hostType, 'other.$fieldName', sourceIsField: true)})'; + }).toList(); + _writeFunctionDefinition(indent, classDefinition.name, + scope: classDefinition.name, + parameters: ['const ${classDefinition.name}& other'], + initializers: initializerStrings); + } + + void _writeAssignmentOperator(Root root, Indent indent, Class classDefinition, + Iterable fields) { + _writeFunctionDefinition(indent, 'operator=', + scope: classDefinition.name, + returnType: '${classDefinition.name}&', + parameters: ['const ${classDefinition.name}& other'], body: () { + for (final NamedType field in fields) { + final HostDatatype hostDatatype = + getFieldHostDatatype(field, _shortBaseCppTypeForBuiltinDartType); + + final String ivarName = _makeInstanceVariableName(field); + final String otherIvar = 'other.$ivarName'; + final String valueExpression; + if (_isPointerField(hostDatatype)) { + final String constructor = + 'std::make_unique<${hostDatatype.datatype}>(*$otherIvar)'; + valueExpression = hostDatatype.isNullable + ? '$otherIvar ? $constructor : nullptr' + : constructor; + } else { + valueExpression = otherIvar; + } + indent.writeln('$ivarName = $valueExpression;'); + } + indent.writeln('return *this;'); + }); + } + void _writeCppSourceClassField(CppOptions generatorOptions, Root root, Indent indent, Class classDefinition, NamedType field) { final HostDatatype hostDatatype = getFieldHostDatatype(field, _shortBaseCppTypeForBuiltinDartType); final String instanceVariableName = _makeInstanceVariableName(field); final String setterName = _makeSetterName(field); - final String returnExpression = hostDatatype.isNullable - ? '$instanceVariableName ? &(*$instanceVariableName) : nullptr' - : instanceVariableName; + final String returnExpression; + if (_isPointerField(hostDatatype)) { + // Convert std::unique_ptr to either T* or const T&. + returnExpression = hostDatatype.isNullable + ? '$instanceVariableName.get()' + : '*$instanceVariableName'; + } else if (hostDatatype.isNullable) { + // Convert std::optional to T*. + returnExpression = + '$instanceVariableName ? &(*$instanceVariableName) : nullptr'; + } else { + returnExpression = instanceVariableName; + } // Writes a setter treating the type as [type], to allow generating multiple // setter variants. @@ -1223,10 +1310,20 @@ return EncodableValue(EncodableList{ /// Returns the value to use when setting a field of the given type from /// an argument of that type. /// - /// For non-nullable values this is just the variable itself, but for nullable - /// values this handles the conversion between an argument type (a pointer) - /// and the field type (a std::optional). - String _fieldValueExpression(HostDatatype type, String variable) { + /// For non-nullable and non-custom-class values this is just the variable + /// itself, but for other values this handles the conversion between an + /// argument type (a pointer or value/reference) and the field type + /// (a std::optional or std::unique_ptr). + String _fieldValueExpression(HostDatatype type, String variable, + {bool sourceIsField = false}) { + if (_isPointerField(type)) { + final String constructor = 'std::make_unique<${type.datatype}>'; + // If the source is a pointer field, it always needs dereferencing. + final String maybeDereference = sourceIsField ? '*' : ''; + return type.isNullable + ? '$variable ? $constructor(*$variable) : nullptr' + : '$constructor($maybeDereference$variable)'; + } return type.isNullable ? '$variable ? ${_valueType(type)}(*$variable) : std::nullopt' : variable; @@ -1312,7 +1409,8 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));'''; if (!hostType.isBuiltin && root.classes.any((Class c) => c.name == dartType.baseName)) { if (preSerializeClasses) { - final String operator = hostType.isNullable ? '->' : '.'; + final String operator = + hostType.isNullable || _isPointerField(hostType) ? '->' : '.'; encodableValue = 'EncodableValue($variableName${operator}ToEncodableList())'; } else { @@ -1550,6 +1648,23 @@ String _valueType(HostDatatype type) { return type.isNullable ? 'std::optional<$baseType>' : baseType; } +/// Returns the C++ type to use when declaring a data class field for the +/// given type. +String _fieldType(HostDatatype type) { + return _isPointerField(type) + ? 'std::unique_ptr<${type.datatype}>' + : _valueType(type); +} + +/// Returns true if [type] should be stored as a pointer, rather than a +/// value type, in a data class. +bool _isPointerField(HostDatatype type) { + // Custom class types are stored as `unique_ptr`s since they can have + // arbitrary size, and can also be arbitrarily (including recursively) + // nested, so must be stored as pointers. + return !type.isBuiltin && !type.isEnum; +} + /// Returns the C++ type to use in an argument context without ownership /// transfer for the given base type. String _unownedArgumentType(HostDatatype type) { @@ -1726,17 +1841,21 @@ void _writeFunctionDeclaration( bool isStatic = false, bool isVirtual = false, bool isConstructor = false, + bool isCopy = false, bool isPureVirtual = false, bool isConst = false, bool isOverride = false, bool deleted = false, + bool defaultImpl = false, bool inlineNoop = false, + bool noexcept = false, void Function()? inlineBody, }) { assert(!(isVirtual && isOverride), 'virtual is redundant with override'); assert(isVirtual || !isPureVirtual, 'pure virtual methods must be virtual'); assert(returnType == null || !isConstructor, 'constructors cannot have return types'); + assert(!(deleted && defaultImpl), 'a function cannot be deleted and default'); _writeFunction( indent, inlineNoop || (inlineBody != null) @@ -1749,12 +1868,14 @@ void _writeFunctionDeclaration( if (inlineBody != null) 'inline', if (isStatic) 'static', if (isVirtual) 'virtual', - if (isConstructor && parameters.isNotEmpty) 'explicit' + if (isConstructor && parameters.isNotEmpty && !isCopy) 'explicit' ], trailingAnnotations: [ if (isConst) 'const', + if (noexcept) 'noexcept', if (isOverride) 'override', if (deleted) '= delete', + if (defaultImpl) '= default', if (isPureVirtual) '= 0', ], body: inlineBody, diff --git a/packages/pigeon/lib/dart/templates.dart b/packages/pigeon/lib/dart/templates.dart new file mode 100644 index 000000000000..ce986b888dbf --- /dev/null +++ b/packages/pigeon/lib/dart/templates.dart @@ -0,0 +1,403 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../generator_tools.dart'; + +/// Creates the `InstanceManager` with the passed string values. +String instanceManagerTemplate({ + required Iterable allProxyApiNames, +}) { + final Iterable apiHandlerSetUps = allProxyApiNames.map( + (String name) { + return '$name.${classMemberNamePrefix}setUpMessageHandlers(${classMemberNamePrefix}instanceManager: instanceManager);'; + }, + ); + + return ''' +/// 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 $instanceManagerClassName { + /// Constructs a [$instanceManagerClassName]. + $instanceManagerClassName({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 [$instanceManagerClassName] used by ProxyApis. + /// + /// On creation, this manager makes a call to clear the native + /// InstanceManager. This is to prevent identifier conflicts after a host + /// restart. + static final $instanceManagerClassName 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 $instanceManagerClassName _initInstance() { + WidgetsFlutterBinding.ensureInitialized(); + final _${instanceManagerClassName}Api api = _${instanceManagerClassName}Api(); + // Clears the native `$instanceManagerClassName` on the initial use of the Dart one. + api.clear(); + final $instanceManagerClassName instanceManager = $instanceManagerClassName( + onWeakReferenceRemoved: (int identifier) { + api.removeStrongReference(identifier); + }, + ); + _${instanceManagerClassName}Api.setUpMessageHandlers(instanceManager: instanceManager); + ${apiHandlerSetUps.join('\n\t\t')} + 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($proxyApiBaseClassName instance) { + final int identifier = _nextUniqueIdentifier(); + _addInstanceWithIdentifier(instance, identifier); + 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($proxyApiBaseClassName 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. + /// + /// This does not remove the weak referenced instance associated with + /// [identifier]. This can be done with [removeWeakReference]. + T? remove(int identifier) { + 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 $proxyApiBaseClassName? weakInstance = _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final $proxyApiBaseClassName? strongInstance = _strongInstances[identifier]; + if (strongInstance != null) { + final $proxyApiBaseClassName copy = strongInstance.${classMemberNamePrefix}copy(); + _identifiers[copy] = identifier; + _weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(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($proxyApiBaseClassName 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. + /// + /// Returns unique identifier of the [instance] added. + void addHostCreatedInstance($proxyApiBaseClassName instance, int identifier) { + _addInstanceWithIdentifier(instance, identifier); + } + + void _addInstanceWithIdentifier($proxyApiBaseClassName instance, int identifier) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + + _identifiers[instance] = identifier; + _weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(instance); + _finalizer.attach(instance, identifier, detach: instance); + + final $proxyApiBaseClassName copy = instance.${classMemberNamePrefix}copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + } + + /// 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; + } +} +'''; +} + +/// Creates the `InstanceManagerApi` with the passed string values. +String instanceManagerApiTemplate({ + required String dartPackageName, + required String pigeonChannelCodecVarName, +}) { + const String apiName = '${instanceManagerClassName}Api'; + + final String removeStrongReferenceName = makeChannelNameWithStrings( + apiName: apiName, + methodName: 'removeStrongReference', + dartPackageName: dartPackageName, + ); + + final String clearName = makeChannelNameWithStrings( + apiName: apiName, + methodName: 'clear', + dartPackageName: dartPackageName, + ); + + return ''' +/// Generated API for managing the Dart and native `$instanceManagerClassName`s. +class _$apiName { + /// Constructor for [_$apiName]. + _$apiName({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec $pigeonChannelCodecVarName = + StandardMessageCodec(); + + static void setUpMessageHandlers({ + BinaryMessenger? binaryMessenger, + $instanceManagerClassName? instanceManager, + }) { + const String channelName = + r'$removeStrongReferenceName'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + $pigeonChannelCodecVarName, + binaryMessenger: binaryMessenger, + ); + channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for \$channelName was null.', + ); + final int? identifier = message as int?; + assert( + identifier != null, + r'Argument for \$channelName, expected non-null int.', + ); + (instanceManager ?? $instanceManagerClassName.instance).remove(identifier!); + return; + }); + } + + Future removeStrongReference(int identifier) async { + const String channelName = + r'$removeStrongReferenceName'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + $pigeonChannelCodecVarName, + binaryMessenger: _binaryMessenger, + ); + final List? replyList = + await channel.send(identifier) as List?; + if (replyList == null) { + throw _createConnectionError(channelName); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Clear the native `$instanceManagerClassName`. + /// + /// This is typically called after a hot restart. + Future clear() async { + const String channelName = + r'$clearName'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + $pigeonChannelCodecVarName, + binaryMessenger: _binaryMessenger, + ); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw _createConnectionError(channelName); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +}'''; +} + +/// The base class for all ProxyApis. +/// +/// All Dart classes generated as a ProxyApi extends this one. +const String proxyApiBaseClass = ''' +/// An immutable object that serves as the base class for all ProxyApis and +/// can provide functional copies of itself. +/// +/// All implementers are expected to be [immutable] as defined by the annotation +/// and override [${classMemberNamePrefix}copy] returning an instance of itself. +@immutable +abstract class $proxyApiBaseClassName { + /// Construct a [$proxyApiBaseClassName]. + $proxyApiBaseClassName({ + this.$_proxyApiBaseClassMessengerVarName, + $instanceManagerClassName? $_proxyApiBaseClassInstanceManagerVarName, + }) : $_proxyApiBaseClassInstanceManagerVarName = + $_proxyApiBaseClassInstanceManagerVarName ?? $instanceManagerClassName.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? $_proxyApiBaseClassMessengerVarName; + + /// Maintains instances stored to communicate with native language objects. + @protected + final $instanceManagerClassName $_proxyApiBaseClassInstanceManagerVarName; + + /// Instantiates and returns a functionally identical object to oneself. + /// + /// Outside of tests, this method should only ever be called by + /// [$instanceManagerClassName]. + /// + /// Subclasses should always override their parent's implementation of this + /// method. + @protected + $proxyApiBaseClassName ${classMemberNamePrefix}copy(); +} +'''; + +/// The base codec for ProxyApis. +/// +/// All generated Dart proxy apis should use this codec or extend it. This codec +/// adds support to convert instances to their corresponding identifier from an +/// `InstanceManager` and vice versa. +const String proxyApiBaseCodec = ''' +class $_proxyApiCodecName extends StandardMessageCodec { + const $_proxyApiCodecName(this.instanceManager); + final $instanceManagerClassName instanceManager; + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is $proxyApiBaseClassName) { + 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); + } + } +} +'''; + +/// Name of the base class of all ProxyApis. +const String proxyApiBaseClassName = '${classNamePrefix}ProxyApiBaseClass'; +const String _proxyApiBaseClassMessengerVarName = + '${classMemberNamePrefix}binaryMessenger'; +const String _proxyApiBaseClassInstanceManagerVarName = + '${classMemberNamePrefix}instanceManager'; +const String _proxyApiCodecName = '_${classNamePrefix}ProxyApiBaseCodec'; diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index 8cdea012554b..40ce7a6e5df4 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:code_builder/code_builder.dart' as cb; +import 'package:dart_style/dart_style.dart'; import 'package:path/path.dart' as path; import 'ast.dart'; +import 'dart/templates.dart'; import 'functional.dart'; import 'generator.dart'; import 'generator_tools.dart'; @@ -18,6 +21,10 @@ const String _docCommentPrefix = '///'; /// user defined parameters. const String _varNamePrefix = '__pigeon_'; +/// Name of the `InstanceManager` variable for a ProxyApi class; +const String _instanceManagerVarName = + '${classMemberNamePrefix}instanceManager'; + /// Name of field used for host API codec. const String _pigeonChannelCodec = 'pigeonChannelCodec'; @@ -111,9 +118,16 @@ class DartGenerator extends StructuredGenerator { "import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;", ); indent.newln(); + + final bool hasProxyApi = root.apis.any((Api api) => api is AstProxyApi); indent.writeln( - "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;"); + "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer${hasProxyApi ? ', immutable, protected' : ''};"); indent.writeln("import 'package:flutter/services.dart';"); + if (hasProxyApi) { + indent.writeln( + "import 'package:flutter/widgets.dart' show WidgetsFlutterBinding;", + ); + } } @override @@ -434,6 +448,158 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; }); } + @override + void writeInstanceManager( + DartOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + indent.format(proxyApiBaseClass); + + indent.format( + instanceManagerTemplate( + allProxyApiNames: root.apis + .whereType() + .map((AstProxyApi api) => api.name), + ), + ); + } + + @override + void writeInstanceManagerApi( + DartOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + indent.format( + instanceManagerApiTemplate( + dartPackageName: dartPackageName, + pigeonChannelCodecVarName: _pigeonChannelCodec, + ), + ); + } + + @override + void writeProxyApiBaseCodec( + DartOptions generatorOptions, + Root root, + Indent indent, + ) { + indent.format(proxyApiBaseCodec); + } + + @override + void writeProxyApi( + DartOptions generatorOptions, + Root root, + Indent indent, + AstProxyApi api, { + required String dartPackageName, + }) { + const String codecName = '_${classNamePrefix}ProxyApiBaseCodec'; + + // Each API has a private codec instance used by every host method, + // constructor, or non-static field. + final String codecInstanceName = '${_varNamePrefix}codec${api.name}'; + + // AST class used by code_builder to generate the code. + final cb.Class proxyApi = cb.Class( + (cb.ClassBuilder builder) => builder + ..name = api.name + ..extend = api.superClass != null + ? cb.refer(api.superClass!.baseName) + : cb.refer(proxyApiBaseClassName) + ..implements.addAll( + api.interfaces.map( + (TypeDeclaration type) => cb.refer(type.baseName), + ), + ) + ..docs.addAll( + asDocumentationComments(api.documentationComments, _docCommentSpec), + ) + ..constructors.addAll(_proxyApiConstructors( + api.constructors, + apiName: api.name, + dartPackageName: dartPackageName, + codecName: codecName, + codecInstanceName: codecInstanceName, + superClassApi: api.superClass?.associatedProxyApi, + unattachedFields: api.unattachedFields, + flutterMethodsFromSuperClasses: api.flutterMethodsFromSuperClasses(), + flutterMethodsFromInterfaces: api.flutterMethodsFromInterfaces(), + declaredFlutterMethods: api.flutterMethods, + )) + ..constructors.add( + _proxyApiDetachedConstructor( + apiName: api.name, + superClassApi: api.superClass?.associatedProxyApi, + unattachedFields: api.unattachedFields, + flutterMethodsFromSuperClasses: + api.flutterMethodsFromSuperClasses(), + flutterMethodsFromInterfaces: api.flutterMethodsFromInterfaces(), + declaredFlutterMethods: api.flutterMethods, + ), + ) + ..fields.addAll([ + if (api.constructors.isNotEmpty || + api.attachedFields.any((ApiField field) => !field.isStatic) || + api.hostMethods.isNotEmpty) + _proxyApiCodecInstanceField( + codecInstanceName: codecInstanceName, + codecName: codecName, + ), + ]) + ..fields.addAll(_proxyApiUnattachedFields(api.unattachedFields)) + ..fields.addAll(_proxyApiFlutterMethodFields( + api.flutterMethods, + apiName: api.name, + )) + ..fields.addAll(_proxyApiInterfaceApiFields(api.apisOfInterfaces())) + ..fields.addAll(_proxyApiAttachedFields(api.attachedFields)) + ..methods.add( + _proxyApiSetUpMessageHandlerMethod( + flutterMethods: api.flutterMethods, + apiName: api.name, + dartPackageName: dartPackageName, + codecName: codecName, + unattachedFields: api.unattachedFields, + hasCallbackConstructor: api.hasCallbackConstructor(), + ), + ) + ..methods.addAll( + _proxyApiAttachedFieldMethods( + api.attachedFields, + apiName: api.name, + dartPackageName: dartPackageName, + codecInstanceName: codecInstanceName, + codecName: codecName, + ), + ) + ..methods.addAll(_proxyApiHostMethods( + api.hostMethods, + apiName: api.name, + dartPackageName: dartPackageName, + codecInstanceName: codecInstanceName, + codecName: codecName, + )) + ..methods.add( + _proxyApiCopyMethod( + apiName: api.name, + unattachedFields: api.unattachedFields, + declaredAndInheritedFlutterMethods: api + .flutterMethodsFromSuperClasses() + .followedBy(api.flutterMethodsFromInterfaces()) + .followedBy(api.flutterMethods), + ), + ), + ); + + final cb.DartEmitter emitter = cb.DartEmitter(useNullSafetySyntax: true); + indent.format(DartFormatter().format('${proxyApi.accept(emitter)}')); + } + /// Generates Dart source code for test support libraries based on the given AST /// represented by [root], outputting the code to [sink]. [sourceOutPath] is the /// path of the generated dart code to be tested. [testOutPath] is where the @@ -521,15 +687,22 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = - root.apis.any((Api api) => api.methods.isNotEmpty && api is AstHostApi); - final bool hasFlutterApi = root.apis - .any((Api api) => api.methods.isNotEmpty && api is AstFlutterApi); - - if (hasHostApi) { + final bool hasHostMethod = root.apis + .whereType() + .any((AstHostApi api) => api.methods.isNotEmpty) || + root.apis.whereType().any((AstProxyApi api) => + api.constructors.isNotEmpty || + api.attachedFields.isNotEmpty || + api.hostMethods.isNotEmpty); + final bool hasFlutterMethod = root.apis + .whereType() + .any((AstFlutterApi api) => api.methods.isNotEmpty) || + root.apis.any((Api api) => api is AstProxyApi); + + if (hasHostMethod) { _writeCreateConnectionError(indent); } - if (hasFlutterApi || generatorOptions.testOutPath != null) { + if (hasFlutterMethod || generatorOptions.testOutPath != null) { _writeWrapResponse(generatorOptions, root, indent); } } @@ -678,7 +851,8 @@ if (${_varNamePrefix}replyList == null) { required bool isMockHandler, required bool isAsynchronous, String nullHandlerExpression = 'api == null', - String Function(String methodName, Iterable safeArgumentNames) + String Function(String methodName, Iterable parameters, + Iterable safeArgumentNames) onCreateApiCall = _createFlutterApiMethodCall, }) { indent.write(''); @@ -745,7 +919,7 @@ if (${_varNamePrefix}replyList == null) { final String name = _getSafeArgumentName(index, field); return '${field.isNamed ? '${field.name}: ' : ''}$name${field.type.isNullable ? '' : '!'}'; }); - call = onCreateApiCall(name, argNames); + call = onCreateApiCall(name, parameters, argNames); } indent.writeScoped('try {', '} ', () { if (returnType.isVoid) { @@ -787,10 +961,820 @@ if (${_varNamePrefix}replyList == null) { static String _createFlutterApiMethodCall( String methodName, + Iterable parameters, Iterable safeArgumentNames, ) { return 'api.$methodName(${safeArgumentNames.join(', ')})'; } + + /// Converts Constructors from the pigeon AST to a `code_builder` Constructor + /// for a ProxyApi. + Iterable _proxyApiConstructors( + Iterable constructors, { + required String apiName, + required String dartPackageName, + required String codecName, + required String codecInstanceName, + required AstProxyApi? superClassApi, + required Iterable unattachedFields, + required Iterable flutterMethodsFromSuperClasses, + required Iterable flutterMethodsFromInterfaces, + required Iterable declaredFlutterMethods, + }) sync* { + final cb.Parameter binaryMessengerParameter = cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}binaryMessenger' + ..named = true + ..toSuper = true, + ); + final cb.Parameter instanceManagerParameter = cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _instanceManagerVarName + ..named = true + ..toSuper = true, + ); + for (final Constructor constructor in constructors) { + yield cb.Constructor( + (cb.ConstructorBuilder builder) { + final String channelName = makeChannelNameWithStrings( + apiName: apiName, + methodName: constructor.name.isNotEmpty + ? constructor.name + : '${classMemberNamePrefix}defaultConstructor', + dartPackageName: dartPackageName, + ); + builder + ..name = constructor.name.isNotEmpty ? constructor.name : null + ..docs.addAll(asDocumentationComments( + constructor.documentationComments, + _docCommentSpec, + )) + ..optionalParameters.addAll( + [ + binaryMessengerParameter, + instanceManagerParameter, + for (final ApiField field in unattachedFields) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = field.name + ..named = true + ..toThis = true + ..required = !field.type.isNullable, + ), + for (final Method method in flutterMethodsFromSuperClasses) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = method.name + ..named = true + ..toSuper = true + ..required = method.isRequired, + ), + for (final Method method in flutterMethodsFromInterfaces + .followedBy(declaredFlutterMethods)) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = method.name + ..named = true + ..toThis = true + ..required = method.isRequired, + ), + ...indexMap( + constructor.parameters, + (int index, NamedType parameter) => cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _getParameterName(index, parameter) + ..type = _refer(parameter.type) + ..named = true + ..required = !parameter.type.isNullable, + ), + ) + ], + ) + ..initializers.addAll( + [ + if (superClassApi != null) + const cb.Code('super.${classMemberNamePrefix}detached()') + ], + ) + ..body = cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageCallSink = StringBuffer(); + _writeHostMethodMessageCall( + Indent(messageCallSink), + channelName: channelName, + parameters: [ + Parameter( + name: '${_varNamePrefix}instanceIdentifier', + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ), + ...unattachedFields.map( + (ApiField field) => Parameter( + name: field.name, + type: field.type, + ), + ), + ...constructor.parameters, + ], + returnType: const TypeDeclaration.voidDeclaration(), + ); + + builder.statements.addAll([ + const cb.Code( + 'final int ${_varNamePrefix}instanceIdentifier = $_instanceManagerVarName.addDartCreatedInstance(this);', + ), + cb.Code('final $codecName $_pigeonChannelCodec =\n' + ' $codecInstanceName;'), + cb.Code( + 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${binaryMessengerParameter.name};', + ), + const cb.Code('() async {'), + cb.Code(messageCallSink.toString()), + const cb.Code('}();'), + ]); + }, + ); + }, + ); + } + } + + /// The detached constructor present for every ProxyApi. + /// + /// This constructor doesn't include a host method call to create a new native + /// class instance. It is mainly used when the native side wants to create a + /// Dart instance or when the `InstanceManager` wants to create a copy for + /// automatic garbage collection. + cb.Constructor _proxyApiDetachedConstructor({ + required String apiName, + required AstProxyApi? superClassApi, + required Iterable unattachedFields, + required Iterable flutterMethodsFromSuperClasses, + required Iterable flutterMethodsFromInterfaces, + required Iterable declaredFlutterMethods, + }) { + final cb.Parameter binaryMessengerParameter = cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}binaryMessenger' + ..named = true + ..toSuper = true, + ); + final cb.Parameter instanceManagerParameter = cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _instanceManagerVarName + ..named = true + ..toSuper = true, + ); + return cb.Constructor( + (cb.ConstructorBuilder builder) => builder + ..name = '${classMemberNamePrefix}detached' + ..docs.addAll([ + '/// Constructs [$apiName] without creating the associated native object.', + '///', + '/// This should only be used by subclasses created by this library or to', + '/// create copies for an [$instanceManagerClassName].', + ]) + ..annotations.add(cb.refer('protected')) + ..optionalParameters.addAll([ + binaryMessengerParameter, + instanceManagerParameter, + for (final ApiField field in unattachedFields) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = field.name + ..named = true + ..toThis = true + ..required = !field.type.isNullable, + ), + for (final Method method in flutterMethodsFromSuperClasses) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = method.name + ..named = true + ..toSuper = true + ..required = method.isRequired, + ), + for (final Method method in flutterMethodsFromInterfaces + .followedBy(declaredFlutterMethods)) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = method.name + ..named = true + ..toThis = true + ..required = method.isRequired, + ), + ]) + ..initializers.addAll([ + if (superClassApi != null) + const cb.Code('super.${classMemberNamePrefix}detached()'), + ]), + ); + } + + /// A private Field of the base codec. + cb.Field _proxyApiCodecInstanceField({ + required String codecInstanceName, + required String codecName, + }) { + return cb.Field( + (cb.FieldBuilder builder) => builder + ..name = codecInstanceName + ..type = cb.refer(codecName) + ..late = true + ..modifier = cb.FieldModifier.final$ + ..assignment = cb.Code('$codecName($_instanceManagerVarName)'), + ); + } + + /// Converts unattached fields from the pigeon AST to `code_builder` + /// Fields. + Iterable _proxyApiUnattachedFields( + Iterable fields, + ) sync* { + for (final ApiField field in fields) { + yield cb.Field( + (cb.FieldBuilder builder) => builder + ..name = field.name + ..type = cb.refer(_addGenericTypesNullable(field.type)) + ..modifier = cb.FieldModifier.final$ + ..docs.addAll(asDocumentationComments( + field.documentationComments, + _docCommentSpec, + )), + ); + } + } + + /// Converts Flutter methods from the pigeon AST to `code_builder` Fields. + /// + /// Flutter methods of a ProxyApi are set as an anonymous function of a class + /// instance, so this converts methods to a `Function` type field instance. + Iterable _proxyApiFlutterMethodFields( + Iterable methods, { + required String apiName, + }) sync* { + for (final Method method in methods) { + yield cb.Field( + (cb.FieldBuilder builder) => builder + ..name = method.name + ..modifier = cb.FieldModifier.final$ + ..docs.addAll(asDocumentationComments( + [ + ...method.documentationComments, + ...[ + if (method.documentationComments.isEmpty) 'Callback method.', + '', + 'For the associated Native object to be automatically garbage collected,', + "it is required that the implementation of this `Function` doesn't have a", + 'strong reference to the encapsulating class instance. When this `Function`', + 'references a non-local variable, it is strongly recommended to access it', + 'with a `WeakReference`:', + '', + '```dart', + 'final WeakReference weakMyVariable = WeakReference(myVariable);', + 'final $apiName instance = $apiName(', + ' ${method.name}: ($apiName ${classMemberNamePrefix}instance, ...) {', + ' print(weakMyVariable?.target);', + ' },', + ');', + '```', + '', + 'Alternatively, [$instanceManagerClassName.removeWeakReference] can be used to', + 'release the associated Native object manually.', + ], + ], + _docCommentSpec, + )) + ..type = cb.FunctionType( + (cb.FunctionTypeBuilder builder) => builder + ..returnType = _refer( + method.returnType, + asFuture: method.isAsynchronous, + ) + ..isNullable = !method.isRequired + ..requiredParameters.addAll([ + cb.refer('$apiName ${classMemberNamePrefix}instance'), + ...indexMap( + method.parameters, + (int index, NamedType parameter) { + return cb.refer( + '${_addGenericTypesNullable(parameter.type)} ${_getParameterName(index, parameter)}', + ); + }, + ), + ]), + ), + ); + } + } + + /// Converts the Flutter methods from the pigeon AST to `code_builder` Fields. + /// + /// Flutter methods of a ProxyApi are set as an anonymous function of a class + /// instance, so this converts methods to a `Function` type field instance. + /// + /// This is similar to [_proxyApiFlutterMethodFields] except all the methods are + /// inherited from apis that are being implemented (following the `implements` + /// keyword). + Iterable _proxyApiInterfaceApiFields( + Iterable apisOfInterfaces, + ) sync* { + for (final AstProxyApi proxyApi in apisOfInterfaces) { + for (final Method method in proxyApi.methods) { + yield cb.Field( + (cb.FieldBuilder builder) => builder + ..name = method.name + ..modifier = cb.FieldModifier.final$ + ..annotations.add(cb.refer('override')) + ..docs.addAll(asDocumentationComments( + method.documentationComments, + _docCommentSpec, + )) + ..type = cb.FunctionType( + (cb.FunctionTypeBuilder builder) => builder + ..returnType = _refer( + method.returnType, + asFuture: method.isAsynchronous, + ) + ..isNullable = !method.isRequired + ..requiredParameters.addAll([ + cb.refer( + '${proxyApi.name} ${classMemberNamePrefix}instance', + ), + ...indexMap( + method.parameters, + (int index, NamedType parameter) { + return cb.refer( + '${_addGenericTypesNullable(parameter.type)} ${_getParameterName(index, parameter)}', + ); + }, + ), + ]), + ), + ); + } + } + } + + /// Converts attached Fields from the pigeon AST to `code_builder` Field. + /// + /// Attached fields are set lazily by calling a private method that returns + /// it. + /// + /// Example Output: + /// + /// ```dart + /// final MyOtherProxyApiClass value = _pigeon_value(); + /// ``` + Iterable _proxyApiAttachedFields(Iterable fields) sync* { + for (final ApiField field in fields) { + yield cb.Field( + (cb.FieldBuilder builder) => builder + ..name = field.name + ..type = cb.refer(_addGenericTypesNullable(field.type)) + ..modifier = cb.FieldModifier.final$ + ..static = field.isStatic + ..late = !field.isStatic + ..docs.addAll(asDocumentationComments( + field.documentationComments, + _docCommentSpec, + )) + ..assignment = cb.Code('$_varNamePrefix${field.name}()'), + ); + } + } + + /// Creates the static `setUpMessageHandlers` method for a ProxyApi. + /// + /// This method handles setting the message handler for every un-inherited + /// Flutter method. + /// + /// This also adds a handler to receive a call from the platform to + /// instantiate a new Dart instance if [hasCallbackConstructor] is set to + /// true. + cb.Method _proxyApiSetUpMessageHandlerMethod({ + required Iterable flutterMethods, + required String apiName, + required String dartPackageName, + required String codecName, + required Iterable unattachedFields, + required bool hasCallbackConstructor, + }) { + final bool hasAnyMessageHandlers = + hasCallbackConstructor || flutterMethods.isNotEmpty; + return cb.Method.returnsVoid( + (cb.MethodBuilder builder) => builder + ..name = '${classMemberNamePrefix}setUpMessageHandlers' + ..returns = cb.refer('void') + ..static = true + ..optionalParameters.addAll([ + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}clearHandlers' + ..type = cb.refer('bool') + ..named = true + ..defaultTo = const cb.Code('false'), + ), + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}binaryMessenger' + ..named = true + ..type = cb.refer('BinaryMessenger?'), + ), + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _instanceManagerVarName + ..named = true + ..type = cb.refer('$instanceManagerClassName?'), + ), + if (hasCallbackConstructor) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}newInstance' + ..named = true + ..type = cb.FunctionType( + (cb.FunctionTypeBuilder builder) => builder + ..returnType = cb.refer(apiName) + ..isNullable = true + ..requiredParameters.addAll( + indexMap( + unattachedFields, + (int index, ApiField field) { + return cb.refer( + '${_addGenericTypesNullable(field.type)} ${_getParameterName(index, field)}', + ); + }, + ), + ), + ), + ), + for (final Method method in flutterMethods) + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = method.name + ..type = cb.FunctionType( + (cb.FunctionTypeBuilder builder) => builder + ..returnType = _refer( + method.returnType, + asFuture: method.isAsynchronous, + ) + ..isNullable = true + ..requiredParameters.addAll([ + cb.refer('$apiName ${classMemberNamePrefix}instance'), + ...indexMap( + method.parameters, + (int index, NamedType parameter) { + return cb.refer( + '${_addGenericTypesNullable(parameter.type)} ${_getParameterName(index, parameter)}', + ); + }, + ), + ]), + ), + ), + ]) + ..body = cb.Block.of([ + if (hasAnyMessageHandlers) ...[ + cb.Code( + 'final $codecName $_pigeonChannelCodec = $codecName($_instanceManagerVarName ?? $instanceManagerClassName.instance);', + ), + const cb.Code( + 'final BinaryMessenger? binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', + ) + ], + if (hasCallbackConstructor) + ...cb.Block((cb.BlockBuilder builder) { + final StringBuffer messageHandlerSink = StringBuffer(); + const String methodName = '${classMemberNamePrefix}newInstance'; + _writeFlutterMethodMessageHandler( + Indent(messageHandlerSink), + name: methodName, + parameters: [ + Parameter( + name: '${classMemberNamePrefix}instanceIdentifier', + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ), + ...unattachedFields.map( + (ApiField field) { + return Parameter(name: field.name, type: field.type); + }, + ), + ], + returnType: const TypeDeclaration.voidDeclaration(), + channelName: makeChannelNameWithStrings( + apiName: apiName, + methodName: methodName, + dartPackageName: dartPackageName, + ), + isMockHandler: false, + isAsynchronous: false, + nullHandlerExpression: '${classMemberNamePrefix}clearHandlers', + onCreateApiCall: ( + String methodName, + Iterable parameters, + Iterable safeArgumentNames, + ) { + final String argsAsNamedParams = map2( + parameters, + safeArgumentNames, + (Parameter parameter, String safeArgName) { + return '${parameter.name}: $safeArgName,\n'; + }, + ).skip(1).join(); + return '($_instanceManagerVarName ?? $instanceManagerClassName.instance)\n' + ' .addHostCreatedInstance(\n' + ' $methodName?.call(${safeArgumentNames.skip(1).join(',')}) ??\n' + ' $apiName.${classMemberNamePrefix}detached(' + ' ${classMemberNamePrefix}binaryMessenger: ${classMemberNamePrefix}binaryMessenger,\n' + ' $_instanceManagerVarName: $_instanceManagerVarName,\n' + ' $argsAsNamedParams\n' + ' ),\n' + ' ${safeArgumentNames.first},\n' + ')'; + }, + ); + builder.statements.add(cb.Code(messageHandlerSink.toString())); + }).statements, + for (final Method method in flutterMethods) + ...cb.Block((cb.BlockBuilder builder) { + final StringBuffer messageHandlerSink = StringBuffer(); + _writeFlutterMethodMessageHandler( + Indent(messageHandlerSink), + name: method.name, + parameters: [ + Parameter( + name: '${classMemberNamePrefix}instance', + type: TypeDeclaration( + baseName: apiName, + isNullable: false, + ), + ), + ...method.parameters, + ], + returnType: TypeDeclaration( + baseName: method.returnType.baseName, + isNullable: + !method.isRequired || method.returnType.isNullable, + typeArguments: method.returnType.typeArguments, + associatedEnum: method.returnType.associatedEnum, + associatedClass: method.returnType.associatedClass, + associatedProxyApi: method.returnType.associatedProxyApi, + ), + channelName: makeChannelNameWithStrings( + apiName: apiName, + methodName: method.name, + dartPackageName: dartPackageName, + ), + isMockHandler: false, + isAsynchronous: method.isAsynchronous, + nullHandlerExpression: '${classMemberNamePrefix}clearHandlers', + onCreateApiCall: ( + String methodName, + Iterable parameters, + Iterable safeArgumentNames, + ) { + final String nullability = method.isRequired ? '' : '?'; + return '($methodName ?? ${safeArgumentNames.first}.$methodName)$nullability.call(${safeArgumentNames.join(',')})'; + }, + ); + builder.statements.add(cb.Code(messageHandlerSink.toString())); + }).statements, + ]), + ); + } + + /// Converts attached fields from the pigeon AST to `code_builder` Methods. + /// + /// These private methods are used to lazily instantiate attached fields. The + /// instance is created and returned synchronously while the native instance + /// is created asynchronously. This is similar to how constructors work. + Iterable _proxyApiAttachedFieldMethods( + Iterable fields, { + required String apiName, + required String dartPackageName, + required String codecInstanceName, + required String codecName, + }) sync* { + for (final ApiField field in fields) { + yield cb.Method( + (cb.MethodBuilder builder) { + final String type = _addGenericTypesNullable(field.type); + const String instanceName = '${_varNamePrefix}instance'; + const String identifierInstanceName = + '${_varNamePrefix}instanceIdentifier'; + builder + ..name = '$_varNamePrefix${field.name}' + ..static = field.isStatic + ..returns = cb.refer(type) + ..body = cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageCallSink = StringBuffer(); + _writeHostMethodMessageCall( + Indent(messageCallSink), + channelName: makeChannelNameWithStrings( + apiName: apiName, + methodName: field.name, + dartPackageName: dartPackageName, + ), + parameters: [ + if (!field.isStatic) + Parameter( + name: 'this', + type: TypeDeclaration( + baseName: apiName, + isNullable: false, + ), + ), + Parameter( + name: identifierInstanceName, + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ), + ], + returnType: const TypeDeclaration.voidDeclaration(), + ); + builder.statements.addAll([ + if (!field.isStatic) ...[ + cb.Code( + 'final $type $instanceName = $type.${classMemberNamePrefix}detached(\n' + ' pigeon_binaryMessenger: pigeon_binaryMessenger,\n' + ' pigeon_instanceManager: pigeon_instanceManager,\n' + ');', + ), + cb.Code('final $codecName $_pigeonChannelCodec =\n' + ' $codecInstanceName;'), + const cb.Code( + 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', + ), + const cb.Code( + 'final int $identifierInstanceName = $_instanceManagerVarName.addDartCreatedInstance($instanceName);', + ), + ] else ...[ + cb.Code( + 'final $type $instanceName = $type.${classMemberNamePrefix}detached();', + ), + cb.Code( + 'final $codecName $_pigeonChannelCodec = $codecName($instanceManagerClassName.instance);', + ), + const cb.Code( + 'final BinaryMessenger ${_varNamePrefix}binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;', + ), + const cb.Code( + 'final int $identifierInstanceName = $instanceManagerClassName.instance.addDartCreatedInstance($instanceName);', + ), + ], + const cb.Code('() async {'), + cb.Code(messageCallSink.toString()), + const cb.Code('}();'), + const cb.Code('return $instanceName;'), + ]); + }, + ); + }, + ); + } + } + + /// Converts host methods from pigeon AST to `code_builder` Methods. + /// + /// This creates methods like a HostApi except that it includes the calling + /// instance if the method is not static. + Iterable _proxyApiHostMethods( + Iterable methods, { + required String apiName, + required String dartPackageName, + required String codecInstanceName, + required String codecName, + }) sync* { + for (final Method method in methods) { + assert(method.location == ApiLocation.host); + yield cb.Method( + (cb.MethodBuilder builder) => builder + ..name = method.name + ..static = method.isStatic + ..modifier = cb.MethodModifier.async + ..docs.addAll(asDocumentationComments( + method.documentationComments, + _docCommentSpec, + )) + ..returns = _refer(method.returnType, asFuture: true) + ..requiredParameters.addAll( + indexMap( + method.parameters, + (int index, NamedType parameter) => cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _getParameterName(index, parameter) + ..type = cb.refer( + _addGenericTypesNullable(parameter.type), + ), + ), + ), + ) + ..optionalParameters.addAll([ + if (method.isStatic) ...[ + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}binaryMessenger' + ..type = cb.refer('BinaryMessenger?') + ..named = true, + ), + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = _instanceManagerVarName + ..type = cb.refer('$instanceManagerClassName?'), + ), + ], + ]) + ..body = cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageCallSink = StringBuffer(); + _writeHostMethodMessageCall( + Indent(messageCallSink), + channelName: makeChannelNameWithStrings( + apiName: apiName, + methodName: method.name, + dartPackageName: dartPackageName, + ), + parameters: [ + if (!method.isStatic) + Parameter( + name: 'this', + type: TypeDeclaration( + baseName: apiName, + isNullable: false, + ), + ), + ...method.parameters, + ], + returnType: method.returnType, + ); + builder.statements.addAll([ + if (!method.isStatic) + cb.Code('final $codecName $_pigeonChannelCodec =\n' + ' $codecInstanceName;') + else + cb.Code( + 'final $codecName $_pigeonChannelCodec = $codecName($_instanceManagerVarName ?? $instanceManagerClassName.instance);', + ), + const cb.Code( + 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', + ), + cb.Code(messageCallSink.toString()), + ]); + }, + ), + ); + } + } + + /// Creates the copy method for a ProxyApi. + /// + /// This method returns a copy of the instance with all the Flutter methods + /// and unattached fields passed to the new instance. This method is inherited + /// from the base ProxyApi class. + cb.Method _proxyApiCopyMethod({ + required String apiName, + required Iterable unattachedFields, + required Iterable declaredAndInheritedFlutterMethods, + }) { + return cb.Method( + (cb.MethodBuilder builder) => builder + ..name = '${classMemberNamePrefix}copy' + ..returns = cb.refer(apiName) + ..annotations.add(cb.refer('override')) + ..body = cb.Block.of([ + cb + .refer('$apiName.${classMemberNamePrefix}detached') + .call( + [], + { + '${classMemberNamePrefix}binaryMessenger': + cb.refer('${classMemberNamePrefix}binaryMessenger'), + _instanceManagerVarName: cb.refer(_instanceManagerVarName), + for (final ApiField field in unattachedFields) + field.name: cb.refer(field.name), + for (final Method method + in declaredAndInheritedFlutterMethods) + method.name: cb.refer(method.name), + }, + ) + .returned + .statement, + ]), + ); + } +} + +cb.Reference _refer(TypeDeclaration type, {bool asFuture = false}) { + final String symbol = _addGenericTypesNullable(type); + return cb.refer(asFuture ? 'Future<$symbol>' : symbol); } String _escapeForDartSingleQuotedString(String raw) { diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 99da7e0a4d56..4540b5d04d8e 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -13,7 +13,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '17.0.0'; +const String pigeonVersion = '17.3.0'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -164,9 +164,22 @@ class Indent { } } -/// Create the generated channel name for a [func] on a [api]. -String makeChannelName(Api api, Method func, String dartPackageName) { - return 'dev.flutter.pigeon.$dartPackageName.${api.name}.${func.name}'; +/// Create the generated channel name for a [method] on an [api]. +String makeChannelName(Api api, Method method, String dartPackageName) { + return makeChannelNameWithStrings( + apiName: api.name, + methodName: method.name, + dartPackageName: dartPackageName, + ); +} + +/// Create the generated channel name for a method on an api. +String makeChannelNameWithStrings({ + required String apiName, + required String methodName, + required String dartPackageName, +}) { + return 'dev.flutter.pigeon.$dartPackageName.$apiName.$methodName'; } // TODO(tarrinneal): Determine whether HostDataType is needed. @@ -286,7 +299,7 @@ const String seeAlsoWarning = 'See also: https://pub.dev/packages/pigeon'; /// /// This lowers the chances of variable name collisions with user defined /// parameters. -const String classNamePrefix = 'Pigeon_'; +const String classNamePrefix = 'Pigeon'; /// Name for the generated InstanceManager for ProxyApis. /// @@ -541,6 +554,23 @@ void addDocumentationComments( DocumentCommentSpecification commentSpec, { List generatorComments = const [], }) { + asDocumentationComments( + comments, + commentSpec, + generatorComments: generatorComments, + ).forEach(indent.writeln); +} + +/// Formats documentation comments and adds them to current Indent. +/// +/// The [comments] list is meant for comments written in the input dart file. +/// The [generatorComments] list is meant for comments added by the generators. +/// Include white space for all tokens when called, no assumptions are made. +Iterable asDocumentationComments( + Iterable comments, + DocumentCommentSpecification commentSpec, { + List generatorComments = const [], +}) sync* { final List allComments = [ ...comments, if (comments.isNotEmpty && generatorComments.isNotEmpty) '', @@ -549,24 +579,20 @@ void addDocumentationComments( String currentLineOpenToken = commentSpec.openCommentToken; if (allComments.length > 1) { if (commentSpec.closeCommentToken != '') { - indent.writeln(commentSpec.openCommentToken); + yield commentSpec.openCommentToken; currentLineOpenToken = commentSpec.blockContinuationToken; } for (String line in allComments) { if (line.isNotEmpty && line[0] != ' ') { line = ' $line'; } - indent.writeln( - '$currentLineOpenToken$line', - ); + yield '$currentLineOpenToken$line'; } if (commentSpec.closeCommentToken != '') { - indent.writeln(commentSpec.closeCommentToken); + yield commentSpec.closeCommentToken; } } else if (allComments.length == 1) { - indent.writeln( - '$currentLineOpenToken${allComments.first}${commentSpec.closeCommentToken}', - ); + yield '$currentLineOpenToken${allComments.first}${commentSpec.closeCommentToken}'; } } @@ -619,92 +645,6 @@ String? deducePackageName(String mainDartFile) { } } -/// Recursively search for all the interfaces apis from a list of names of -/// interfaces. -/// -/// This method assumes that all interfaces are ProxyApis and an api doesn't -/// contains itself as an interface. Otherwise, throws an [ArgumentError]. -Set recursiveFindAllInterfaceApis( - AstProxyApi api, { - Set seenApis = const {}, -}) { - final Set allInterfaces = {}; - - allInterfaces.addAll( - api.interfaces.map( - (TypeDeclaration type) { - if (!type.isProxyApi) { - throw ArgumentError( - 'Could not find a valid ProxyApi for an interface: $type', - ); - } else if (seenApis.contains(type.associatedProxyApi)) { - throw ArgumentError( - 'A ProxyApi cannot be a super class of itself: ${type.baseName}', - ); - } - return type.associatedProxyApi!; - }, - ), - ); - - // Adds the current api since it would be invalid for it to be an interface - // of itself. - final Set newSeenApis = {...seenApis, api}; - - for (final AstProxyApi interfaceApi in {...allInterfaces}) { - allInterfaces.addAll(recursiveFindAllInterfaceApis( - interfaceApi, - seenApis: newSeenApis, - )); - } - - return allInterfaces; -} - -/// Creates a list of ProxyApis where each `extends` the ProxyApi that follows -/// it. -/// -/// Returns an empty list if [proxyApi] does not extend a ProxyApi. -/// -/// This method assumes the super classes of each ProxyApi doesn't create a -/// loop. Throws a [ArgumentError] if a loop is found. -/// -/// This method also assumes that all super classes are ProxyApis. Otherwise, -/// throws an [ArgumentError]. -List recursiveGetSuperClassApisChain(AstProxyApi api) { - final List superClassChain = []; - - if (api.superClass != null && !api.superClass!.isProxyApi) { - throw ArgumentError( - 'Could not find a ProxyApi for super class: ${api.superClass!.baseName}', - ); - } - - AstProxyApi? currentProxyApi = api.superClass?.associatedProxyApi; - while (currentProxyApi != null) { - if (superClassChain.contains(currentProxyApi)) { - throw ArgumentError( - 'Loop found when processing super classes for a ProxyApi: ' - '${api.name}, ${superClassChain.map((AstProxyApi api) => api.name)}', - ); - } - - superClassChain.add(currentProxyApi); - - if (currentProxyApi.superClass != null && - !currentProxyApi.superClass!.isProxyApi) { - throw ArgumentError( - 'Could not find a ProxyApi for super class: ' - '${currentProxyApi.superClass!.baseName}', - ); - } - - currentProxyApi = currentProxyApi.superClass?.associatedProxyApi; - } - - return superClassChain; -} - /// Enum to specify api type when generating code. enum ApiType { /// Flutter api. diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index 29e3e3662554..39697fa3242d 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -32,6 +32,7 @@ class KotlinOptions { this.package, this.copyrightHeader, this.errorClassName, + this.includeErrorClass = true, }); /// The package where the generated class will live. @@ -43,6 +44,12 @@ class KotlinOptions { /// The name of the error class used for passing custom error parameters. final String? errorClassName; + /// Whether to include the error class in generation. + /// + /// This should only ever be set to false if you have another generated + /// Kotlin file in the same directory. + final bool includeErrorClass; + /// Creates a [KotlinOptions] from a Map representation where: /// `x = KotlinOptions.fromMap(x.toMap())`. static KotlinOptions fromMap(Map map) { @@ -50,6 +57,7 @@ class KotlinOptions { package: map['package'] as String?, copyrightHeader: map['copyrightHeader'] as Iterable?, errorClassName: map['errorClassName'] as String?, + includeErrorClass: map['includeErrorClass'] as bool? ?? true, ); } @@ -60,6 +68,7 @@ class KotlinOptions { if (package != null) 'package': package!, if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, if (errorClassName != null) 'errorClassName': errorClassName!, + 'includeErrorClass': includeErrorClass, }; return result; } @@ -298,7 +307,7 @@ class KotlinGenerator extends StructuredGenerator { addDocumentationComments( indent, field.documentationComments, _docCommentSpec); indent.write( - 'val ${field.name}: ${_nullsafeKotlinTypeForDartType(field.type)}'); + 'val ${field.name}: ${_nullSafeKotlinTypeForDartType(field.type)}'); final String defaultNil = field.type.isNullable ? ' = null' : ''; indent.add(defaultNil); } @@ -361,16 +370,15 @@ class KotlinGenerator extends StructuredGenerator { }); }); - final String errorClassName = _getErrorClassName(generatorOptions); for (final Method method in api.methods) { _writeFlutterMethod( indent, + generatorOptions: generatorOptions, name: method.name, parameters: method.parameters, returnType: method.returnType, channelName: makeChannelName(api, method, dartPackageName), documentationComments: method.documentationComments, - errorClassName: errorClassName, dartPackageName: dartPackageName, ); } @@ -588,7 +596,9 @@ class KotlinGenerator extends StructuredGenerator { if (hasFlutterApi) { _writeCreateConnectionError(generatorOptions, indent); } - _writeErrorClass(generatorOptions, indent); + if (generatorOptions.includeErrorClass) { + _writeErrorClass(generatorOptions, indent); + } } static void _writeMethodDeclaration( @@ -606,7 +616,7 @@ class KotlinGenerator extends StructuredGenerator { final List argSignature = []; if (parameters.isNotEmpty) { final Iterable argTypes = parameters - .map((NamedType e) => _nullsafeKotlinTypeForDartType(e.type)); + .map((NamedType e) => _nullSafeKotlinTypeForDartType(e.type)); final Iterable argNames = indexMap(parameters, getArgumentName); argSignature.addAll( map2( @@ -620,7 +630,7 @@ class KotlinGenerator extends StructuredGenerator { } final String returnTypeString = - returnType.isVoid ? '' : _nullsafeKotlinTypeForDartType(returnType); + returnType.isVoid ? '' : _nullSafeKotlinTypeForDartType(returnType); final String resultType = returnType.isVoid ? 'Unit' : returnTypeString; addDocumentationComments(indent, documentationComments, _docCommentSpec); @@ -700,7 +710,7 @@ class KotlinGenerator extends StructuredGenerator { indent.write('$call '); final String resultType = returnType.isVoid ? 'Unit' - : _nullsafeKotlinTypeForDartType(returnType); + : _nullSafeKotlinTypeForDartType(returnType); indent.addScoped('{ result: Result<$resultType> ->', '}', () { indent.writeln('val error = result.exceptionOrNull()'); indent.writeScoped('if (error != null) {', '}', () { @@ -751,11 +761,11 @@ class KotlinGenerator extends StructuredGenerator { void _writeFlutterMethod( Indent indent, { + required KotlinOptions generatorOptions, required String name, required List parameters, required TypeDeclaration returnType, required String channelName, - required String errorClassName, required String dartPackageName, List documentationComments = const [], int? minApiRequirement, @@ -778,6 +788,7 @@ class KotlinGenerator extends StructuredGenerator { getArgumentName: _getSafeArgumentName, ); + final String errorClassName = _getErrorClassName(generatorOptions); indent.addScoped('{', '}', () { onWriteBody( indent, @@ -910,9 +921,9 @@ String _kotlinTypeForBuiltinGenericDartType(TypeDeclaration type) { } else { switch (type.baseName) { case 'List': - return 'List<${_nullsafeKotlinTypeForDartType(type.typeArguments.first)}>'; + return 'List<${_nullSafeKotlinTypeForDartType(type.typeArguments.first)}>'; case 'Map': - return 'Map<${_nullsafeKotlinTypeForDartType(type.typeArguments.first)}, ${_nullsafeKotlinTypeForDartType(type.typeArguments.last)}>'; + return 'Map<${_nullSafeKotlinTypeForDartType(type.typeArguments.first)}, ${_nullSafeKotlinTypeForDartType(type.typeArguments.last)}>'; default: return '${type.baseName}<${_flattenTypeArguments(type.typeArguments)}>'; } @@ -946,7 +957,7 @@ String _kotlinTypeForDartType(TypeDeclaration type) { return _kotlinTypeForBuiltinDartType(type) ?? type.baseName; } -String _nullsafeKotlinTypeForDartType(TypeDeclaration type) { +String _nullSafeKotlinTypeForDartType(TypeDeclaration type) { final String nullSafe = type.isNullable ? '?' : ''; return '${_kotlinTypeForDartType(type)}$nullSafe'; } @@ -969,7 +980,7 @@ String _cast(Indent indent, String variable, {required TypeDeclaration type}) { } return '${type.baseName}.ofRaw($variable as Int)!!'; } - return '$variable as ${_nullsafeKotlinTypeForDartType(type)}'; + return '$variable as ${_nullSafeKotlinTypeForDartType(type)}'; } String _castInt(bool isNullable) { diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index bc26495e650b..67c93a74f3a9 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -359,7 +359,7 @@ class ObjcHeaderGenerator extends StructuredGenerator { String? lastArgType; String? returnType; final String enumReturnType = _enumName( - returnTypeName.baseName, + func.returnType.baseName, suffix: ' *_Nullable', prefix: generatorOptions.prefix, box: true, @@ -715,7 +715,10 @@ class ObjcSourceGenerator extends StructuredGenerator { _writeCreateConnectionError(indent); indent.newln(); } - _writeGetNullableObjectAtIndex(indent); + + if (hasHostApi || hasFlutterApi) { + _writeGetNullableObjectAtIndex(indent); + } } void _writeWrapError(Indent indent) { @@ -765,7 +768,7 @@ static FlutterError *createConnectionError(NSString *channelName) { } else if (arg.type.isEnum) { indent.writeln('NSNumber *${argName}AsNumber = $valueGetter;'); indent.writeln( - '${_enumName(arg.type.baseName, suffix: ' *', prefix: '', box: true)}$argName = ${argName}AsNumber == nil ? nil : [[${_enumName(arg.type.baseName, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[${argName}AsNumber integerValue]];'); + '${_enumName(arg.type.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)}$argName = ${argName}AsNumber == nil ? nil : [[${_enumName(arg.type.baseName, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[${argName}AsNumber integerValue]];'); } else { indent.writeln('${objcArgType.beforeString}$argName = $valueGetter;'); } @@ -799,7 +802,7 @@ static FlutterError *createConnectionError(NSString *channelName) { if (func.returnType.isEnum) { returnTypeString = - '${_enumName(returnType.baseName, suffix: ' *_Nullable', prefix: generatorOptions.prefix, box: true)} enumValue'; + '${_enumName(func.returnType.baseName, suffix: ' *_Nullable', prefix: generatorOptions.prefix, box: true)} enumValue'; } if (func.parameters.isEmpty) { indent.writeScoped( @@ -1132,7 +1135,7 @@ static FlutterError *createConnectionError(NSString *channelName) { indent.writeln('completion(nil);'); } else { if (func.returnType.isEnum) { - final String enumName = _enumName(returnType.baseName, + final String enumName = _enumName(func.returnType.baseName, prefix: languageOptions.prefix, box: true); indent.writeln('NSNumber *outputAsNumber = $nullCheck;'); indent.writeln( @@ -1212,7 +1215,7 @@ String _callbackForType( if (type.isVoid) { return 'void (^)(FlutterError *_Nullable)'; } else if (type.isEnum) { - return 'void (^)(${_enumName(objcType.baseName, suffix: ' *_Nullable', prefix: options.prefix, box: true)}, FlutterError *_Nullable)'; + return 'void (^)(${_enumName(type.baseName, suffix: ' *_Nullable', prefix: options.prefix, box: true)}, FlutterError *_Nullable)'; } else { return 'void (^)(${objcType.beforeString}_Nullable, FlutterError *_Nullable)'; } diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart index a2c6026e3117..7f2fb7505fa8 100644 --- a/packages/pigeon/lib/pigeon.dart +++ b/packages/pigeon/lib/pigeon.dart @@ -9,7 +9,5 @@ export 'dart_generator.dart' show DartOptions; export 'java_generator.dart' show JavaOptions; export 'kotlin_generator.dart' show KotlinOptions; export 'objc_generator.dart' show ObjcOptions; -// TODO(bparrishMines): Remove hide once implementation of the api is finished -// for Dart and one host language. -export 'pigeon_lib.dart' hide ProxyApi; +export 'pigeon_lib.dart'; export 'swift_generator.dart' show SwiftOptions; diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 187aa3466b2a..addce55d54af 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -177,6 +177,12 @@ class SwiftFunction { final String value; } +/// Metadata to annotate data classes to be defined as class in Swift output. +class SwiftClass { + /// Constructor. + const SwiftClass(); +} + /// Type of TaskQueue which determines how handlers are dispatched for /// HostApi's. enum TaskQueueType { @@ -776,6 +782,7 @@ class KotlinGeneratorAdapter implements GeneratorAdapter { options.kotlinOptions ?? const KotlinOptions(); kotlinOptions = kotlinOptions.merge(KotlinOptions( errorClassName: kotlinOptions.errorClassName ?? 'FlutterError', + includeErrorClass: kotlinOptions.includeErrorClass, copyrightHeader: options.copyrightHeader != null ? _lineReader( path.posix.join(options.basePath ?? '', options.copyrightHeader)) @@ -1029,10 +1036,10 @@ List _validateProxyApi( } // Validate this api isn't used as an interface and contains anything except - // Flutter methods. - final bool isValidInterfaceProxyApi = api.hostMethods.isEmpty && - api.constructors.isEmpty && - api.fields.isEmpty; + // Flutter methods, a static host method, attached methods. + final bool isValidInterfaceProxyApi = api.constructors.isEmpty && + api.fields.where((ApiField field) => !field.isStatic).isEmpty && + api.hostMethods.where((Method method) => !method.isStatic).isEmpty; if (!isValidInterfaceProxyApi) { final Iterable interfaceNames = proxyApi.interfaces.map( (TypeDeclaration type) => type.baseName, @@ -1549,6 +1556,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { _currentClass = Class( name: node.name.lexeme, fields: [], + isSwiftClass: _hasMetadata(node.metadata, 'SwiftClass'), documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 61e2ea20faf3..791cdd2e9365 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -125,11 +125,26 @@ class SwiftGenerator extends StructuredGenerator { indent, classDefinition.documentationComments, _docCommentSpec, generatorComments: generatedComments); - indent.write('struct ${classDefinition.name} '); + if (classDefinition.isSwiftClass) { + indent.write('class ${classDefinition.name} '); + } else { + indent.write('struct ${classDefinition.name} '); + } indent.addScoped('{', '}', () { - getFieldsInSerializationOrder(classDefinition).forEach((NamedType field) { - _writeClassField(indent, field); - }); + final Iterable fields = + getFieldsInSerializationOrder(classDefinition); + + if (classDefinition.isSwiftClass) { + _writeClassInit(indent, fields.toList()); + } + + for (final NamedType field in fields) { + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + indent.write('var '); + _writeClassField(indent, field, addNil: !classDefinition.isSwiftClass); + indent.newln(); + } indent.newln(); writeClassDecode( @@ -149,6 +164,35 @@ class SwiftGenerator extends StructuredGenerator { }); } + void _writeClassInit(Indent indent, List fields) { + indent.writeScoped('init(', ')', () { + for (int i = 0; i < fields.length; i++) { + indent.write(''); + _writeClassField(indent, fields[i]); + if (i == fields.length - 1) { + indent.newln(); + } else { + indent.addln(','); + } + } + }, addTrailingNewline: false); + indent.addScoped(' {', '}', () { + for (final NamedType field in fields) { + _writeClassFieldInit(indent, field); + } + }); + } + + void _writeClassField(Indent indent, NamedType field, {bool addNil = true}) { + indent.add('${field.name}: ${_nullsafeSwiftTypeForDartType(field.type)}'); + final String defaultNil = field.type.isNullable && addNil ? ' = nil' : ''; + indent.add(defaultNil); + } + + void _writeClassFieldInit(Indent indent, NamedType field) { + indent.writeln('self.${field.name} = ${field.name}'); + } + @override void writeClassEncode( SwiftOptions generatorOptions, @@ -222,16 +266,6 @@ class SwiftGenerator extends StructuredGenerator { }); } - void _writeClassField(Indent indent, NamedType field) { - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - - indent.write( - 'var ${field.name}: ${_nullsafeSwiftTypeForDartType(field.type)}'); - final String defaultNil = field.type.isNullable ? ' = nil' : ''; - indent.addln(defaultNil); - } - @override void writeApis( SwiftOptions generatorOptions, @@ -263,17 +297,6 @@ class SwiftGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - /// Returns an argument name that can be used in a context where it is possible to collide. - String getEnumSafeArgumentExpression( - Root root, int count, NamedType argument) { - String enumTag = ''; - if (argument.type.isEnum) { - enumTag = argument.type.isNullable ? '?.rawValue' : '.rawValue'; - } - - return '${_getArgumentName(count, argument)}Arg$enumTag'; - } - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; if (isCustomCodec) { _writeCodec(indent, api, root); @@ -289,7 +312,15 @@ class SwiftGenerator extends StructuredGenerator { for (final Method func in api.methods) { addDocumentationComments( indent, func.documentationComments, _docCommentSpec); - indent.writeln(_getMethodSignature(func)); + indent.writeln(_getMethodSignature( + name: func.name, + parameters: func.parameters, + returnType: func.returnType, + errorTypeName: 'FlutterError', + isAsynchronous: true, + swiftFunction: func.swiftFunction, + getParameterName: _getSafeArgumentName, + )); } }); @@ -309,65 +340,19 @@ class SwiftGenerator extends StructuredGenerator { indent.writeln('return $codecName.shared'); }); } + for (final Method func in api.methods) { addDocumentationComments( indent, func.documentationComments, _docCommentSpec); - indent.writeScoped('${_getMethodSignature(func)} {', '}', () { - final Iterable enumSafeArgNames = func.parameters - .asMap() - .entries - .map((MapEntry e) => - getEnumSafeArgumentExpression(root, e.key, e.value)); - final String sendArgument = func.parameters.isEmpty - ? 'nil' - : '[${enumSafeArgNames.join(', ')}] as [Any?]'; - const String channel = 'channel'; - indent.writeln( - 'let channelName: String = "${makeChannelName(api, func, dartPackageName)}"'); - indent.writeln( - 'let $channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger$codecArgumentString)'); - indent.write('$channel.sendMessage($sendArgument) '); - - indent.addScoped('{ response in', '}', () { - indent.writeScoped( - 'guard let listResponse = response as? [Any?] else {', '}', () { - indent.writeln( - 'completion(.failure(createConnectionError(withChannelName: channelName)))'); - indent.writeln('return'); - }); - indent.writeScoped('if listResponse.count > 1 {', '} ', () { - indent.writeln('let code: String = listResponse[0] as! String'); - indent.writeln( - 'let message: String? = nilOrValue(listResponse[1])'); - indent.writeln( - 'let details: String? = nilOrValue(listResponse[2])'); - indent.writeln( - 'completion(.failure(FlutterError(code: code, message: message, details: details)))'); - }, addTrailingNewline: false); - if (!func.returnType.isNullable && !func.returnType.isVoid) { - indent.addScoped('else if listResponse[0] == nil {', '} ', () { - indent.writeln( - 'completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))'); - }, addTrailingNewline: false); - } - indent.addScoped('else {', '}', () { - if (func.returnType.isVoid) { - indent.writeln('completion(.success(Void()))'); - } else { - final String fieldType = _swiftTypeForDartType(func.returnType); - - _writeGenericCasting( - indent: indent, - value: 'listResponse[0]', - variableName: 'result', - fieldType: fieldType, - type: func.returnType); - - indent.writeln('completion(.success(result))'); - } - }); - }); - }); + _writeFlutterMethod( + indent, + name: func.name, + channelName: makeChannelName(api, func, dartPackageName), + parameters: func.parameters, + returnType: func.returnType, + codecArgumentString: codecArgumentString, + swiftFunction: func.swiftFunction, + ); } }); } @@ -400,37 +385,16 @@ class SwiftGenerator extends StructuredGenerator { indent.write('protocol $apiName '); indent.addScoped('{', '}', () { for (final Method method in api.methods) { - final _SwiftFunctionComponents components = - _SwiftFunctionComponents.fromMethod(method); - final List argSignature = - components.arguments.map((_SwiftFunctionArgument argument) { - final String? label = argument.label; - final String name = argument.name; - final String type = _nullsafeSwiftTypeForDartType(argument.type); - return '${label == null ? '' : '$label '}$name: $type'; - }).toList(); - - final String returnType = method.returnType.isVoid - ? 'Void' - : _nullsafeSwiftTypeForDartType(method.returnType); - - final String escapeType = - method.returnType.isVoid ? 'Void' : returnType; - addDocumentationComments( indent, method.documentationComments, _docCommentSpec); - - if (method.isAsynchronous) { - argSignature.add( - 'completion: @escaping (Result<$escapeType, Error>) -> Void'); - indent.writeln('func ${components.name}(${argSignature.join(', ')})'); - } else if (method.returnType.isVoid) { - indent.writeln( - 'func ${components.name}(${argSignature.join(', ')}) throws'); - } else { - indent.writeln( - 'func ${components.name}(${argSignature.join(', ')}) throws -> $returnType'); - } + indent.writeln(_getMethodSignature( + name: method.name, + parameters: method.parameters, + returnType: method.returnType, + errorTypeName: 'Error', + isAsynchronous: method.isAsynchronous, + swiftFunction: method.swiftFunction, + )); } }); @@ -453,108 +417,17 @@ class SwiftGenerator extends StructuredGenerator { 'static func setUp(binaryMessenger: FlutterBinaryMessenger, api: $apiName?) '); indent.addScoped('{', '}', () { for (final Method method in api.methods) { - final _SwiftFunctionComponents components = - _SwiftFunctionComponents.fromMethod(method); - - final String channelName = - makeChannelName(api, method, dartPackageName); - final String varChannelName = '${method.name}Channel'; - addDocumentationComments( - indent, method.documentationComments, _docCommentSpec); - - indent.writeln( - 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); - indent.write('if let api = api '); - indent.addScoped('{', '}', () { - indent.write('$varChannelName.setMessageHandler '); - final String messageVarName = - method.parameters.isNotEmpty ? 'message' : '_'; - indent.addScoped('{ $messageVarName, reply in', '}', () { - final List methodArgument = []; - if (components.arguments.isNotEmpty) { - indent.writeln('let args = message as! [Any?]'); - enumerate(components.arguments, - (int index, _SwiftFunctionArgument arg) { - final String argName = - _getSafeArgumentName(index, arg.namedType); - final String argIndex = 'args[$index]'; - final String fieldType = _swiftTypeForDartType(arg.type); - - _writeGenericCasting( - indent: indent, - value: argIndex, - variableName: argName, - fieldType: fieldType, - type: arg.type); - - if (arg.label == '_') { - methodArgument.add(argName); - } else { - methodArgument.add('${arg.label ?? arg.name}: $argName'); - } - }); - } - final String tryStatement = method.isAsynchronous ? '' : 'try '; - // Empty parens are not required when calling a method whose only - // argument is a trailing closure. - final String argumentString = - methodArgument.isEmpty && method.isAsynchronous - ? '' - : '(${methodArgument.join(', ')})'; - final String call = - '${tryStatement}api.${components.name}$argumentString'; - if (method.isAsynchronous) { - final String resultName = - method.returnType.isVoid ? 'nil' : 'res'; - final String successVariableInit = - method.returnType.isVoid ? '' : '(let res)'; - indent.write('$call '); - - indent.addScoped('{ result in', '}', () { - indent.write('switch result '); - indent.addScoped('{', '}', nestCount: 0, () { - final String nullsafe = - method.returnType.isNullable ? '?' : ''; - final String enumTag = - method.returnType.isEnum ? '$nullsafe.rawValue' : ''; - indent.writeln('case .success$successVariableInit:'); - indent.nest(1, () { - indent.writeln('reply(wrapResult($resultName$enumTag))'); - }); - indent.writeln('case .failure(let error):'); - indent.nest(1, () { - indent.writeln('reply(wrapError(error))'); - }); - }); - }); - } else { - indent.write('do '); - indent.addScoped('{', '}', () { - if (method.returnType.isVoid) { - indent.writeln(call); - indent.writeln('reply(wrapResult(nil))'); - } else { - String enumTag = ''; - if (method.returnType.isEnum) { - enumTag = '.rawValue'; - } - enumTag = - method.returnType.isNullable && method.returnType.isEnum - ? '?$enumTag' - : enumTag; - indent.writeln('let result = $call'); - indent.writeln('reply(wrapResult(result$enumTag))'); - } - }, addTrailingNewline: false); - indent.addScoped(' catch {', '}', () { - indent.writeln('reply(wrapError(error))'); - }); - } - }); - }, addTrailingNewline: false); - indent.addScoped(' else {', '}', () { - indent.writeln('$varChannelName.setMessageHandler(nil)'); - }); + _writeHostMethodMessageHandler( + indent, + name: method.name, + channelName: makeChannelName(api, method, dartPackageName), + parameters: method.parameters, + returnType: method.returnType, + isAsynchronous: method.isAsynchronous, + codecArgumentString: codecArgumentString, + swiftFunction: method.swiftFunction, + documentationComments: method.documentationComments, + ); } }); }); @@ -825,6 +698,199 @@ private func nilOrValue(_ value: Any?) -> T? { _writeIsNullish(indent); _writeNilOrValue(indent); } + + void _writeFlutterMethod( + Indent indent, { + required String name, + required String channelName, + required List parameters, + required TypeDeclaration returnType, + required String codecArgumentString, + required String? swiftFunction, + }) { + final String methodSignature = _getMethodSignature( + name: name, + parameters: parameters, + returnType: returnType, + errorTypeName: 'FlutterError', + isAsynchronous: true, + swiftFunction: swiftFunction, + getParameterName: _getSafeArgumentName, + ); + + /// Returns an argument name that can be used in a context where it is possible to collide. + String getEnumSafeArgumentExpression(int count, NamedType argument) { + String enumTag = ''; + if (argument.type.isEnum) { + enumTag = argument.type.isNullable ? '?.rawValue' : '.rawValue'; + } + + return '${_getArgumentName(count, argument)}Arg$enumTag'; + } + + indent.writeScoped('$methodSignature {', '}', () { + final Iterable enumSafeArgNames = parameters.asMap().entries.map( + (MapEntry e) => + getEnumSafeArgumentExpression(e.key, e.value)); + final String sendArgument = parameters.isEmpty + ? 'nil' + : '[${enumSafeArgNames.join(', ')}] as [Any?]'; + const String channel = 'channel'; + indent.writeln('let channelName: String = "$channelName"'); + indent.writeln( + 'let $channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger$codecArgumentString)'); + indent.write('$channel.sendMessage($sendArgument) '); + + indent.addScoped('{ response in', '}', () { + indent.writeScoped( + 'guard let listResponse = response as? [Any?] else {', '}', () { + indent.writeln( + 'completion(.failure(createConnectionError(withChannelName: channelName)))'); + indent.writeln('return'); + }); + indent.writeScoped('if listResponse.count > 1 {', '} ', () { + indent.writeln('let code: String = listResponse[0] as! String'); + indent.writeln('let message: String? = nilOrValue(listResponse[1])'); + indent.writeln('let details: String? = nilOrValue(listResponse[2])'); + indent.writeln( + 'completion(.failure(FlutterError(code: code, message: message, details: details)))'); + }, addTrailingNewline: false); + if (!returnType.isNullable && !returnType.isVoid) { + indent.addScoped('else if listResponse[0] == nil {', '} ', () { + indent.writeln( + 'completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))'); + }, addTrailingNewline: false); + } + indent.addScoped('else {', '}', () { + if (returnType.isVoid) { + indent.writeln('completion(.success(Void()))'); + } else { + final String fieldType = _swiftTypeForDartType(returnType); + + _writeGenericCasting( + indent: indent, + value: 'listResponse[0]', + variableName: 'result', + fieldType: fieldType, + type: returnType, + ); + + indent.writeln('completion(.success(result))'); + } + }); + }); + }); + } + + void _writeHostMethodMessageHandler( + Indent indent, { + required String name, + required String channelName, + required Iterable parameters, + required TypeDeclaration returnType, + required bool isAsynchronous, + required String codecArgumentString, + required String? swiftFunction, + List documentationComments = const [], + }) { + final _SwiftFunctionComponents components = _SwiftFunctionComponents( + name: name, + parameters: parameters, + returnType: returnType, + swiftFunction: swiftFunction, + ); + + final String varChannelName = '${name}Channel'; + addDocumentationComments(indent, documentationComments, _docCommentSpec); + + indent.writeln( + 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); + indent.write('if let api = api '); + indent.addScoped('{', '}', () { + indent.write('$varChannelName.setMessageHandler '); + final String messageVarName = parameters.isNotEmpty ? 'message' : '_'; + indent.addScoped('{ $messageVarName, reply in', '}', () { + final List methodArgument = []; + if (components.arguments.isNotEmpty) { + indent.writeln('let args = message as! [Any?]'); + enumerate(components.arguments, + (int index, _SwiftFunctionArgument arg) { + final String argName = _getSafeArgumentName(index, arg.namedType); + final String argIndex = 'args[$index]'; + final String fieldType = _swiftTypeForDartType(arg.type); + + _writeGenericCasting( + indent: indent, + value: argIndex, + variableName: argName, + fieldType: fieldType, + type: arg.type); + + if (arg.label == '_') { + methodArgument.add(argName); + } else { + methodArgument.add('${arg.label ?? arg.name}: $argName'); + } + }); + } + final String tryStatement = isAsynchronous ? '' : 'try '; + // Empty parens are not required when calling a method whose only + // argument is a trailing closure. + final String argumentString = methodArgument.isEmpty && isAsynchronous + ? '' + : '(${methodArgument.join(', ')})'; + final String call = + '${tryStatement}api.${components.name}$argumentString'; + if (isAsynchronous) { + final String resultName = returnType.isVoid ? 'nil' : 'res'; + final String successVariableInit = + returnType.isVoid ? '' : '(let res)'; + indent.write('$call '); + + indent.addScoped('{ result in', '}', () { + indent.write('switch result '); + indent.addScoped('{', '}', nestCount: 0, () { + final String nullsafe = returnType.isNullable ? '?' : ''; + final String enumTag = + returnType.isEnum ? '$nullsafe.rawValue' : ''; + indent.writeln('case .success$successVariableInit:'); + indent.nest(1, () { + indent.writeln('reply(wrapResult($resultName$enumTag))'); + }); + indent.writeln('case .failure(let error):'); + indent.nest(1, () { + indent.writeln('reply(wrapError(error))'); + }); + }); + }); + } else { + indent.write('do '); + indent.addScoped('{', '}', () { + if (returnType.isVoid) { + indent.writeln(call); + indent.writeln('reply(wrapResult(nil))'); + } else { + String enumTag = ''; + if (returnType.isEnum) { + enumTag = '.rawValue'; + } + enumTag = returnType.isNullable && returnType.isEnum + ? '?$enumTag' + : enumTag; + indent.writeln('let result = $call'); + indent.writeln('reply(wrapResult(result$enumTag))'); + } + }, addTrailingNewline: false); + indent.addScoped(' catch {', '}', () { + indent.writeln('reply(wrapError(error))'); + }); + } + }); + }, addTrailingNewline: false); + indent.addScoped(' else {', '}', () { + indent.writeln('$varChannelName.setMessageHandler(nil)'); + }); + } } /// Calculates the name of the codec that will be generated for [api]. @@ -902,28 +968,49 @@ String _nullsafeSwiftTypeForDartType(TypeDeclaration type) { return '${_swiftTypeForDartType(type)}$nullSafe'; } -String _getMethodSignature(Method func) { - final _SwiftFunctionComponents components = - _SwiftFunctionComponents.fromMethod(func); - final String returnType = func.returnType.isVoid - ? 'Void' - : _nullsafeSwiftTypeForDartType(func.returnType); - - if (func.parameters.isEmpty) { - return 'func ${func.name}(completion: @escaping (Result<$returnType, FlutterError>) -> Void)'; +String _getMethodSignature({ + required String name, + required Iterable parameters, + required TypeDeclaration returnType, + required String errorTypeName, + bool isAsynchronous = false, + String? swiftFunction, + String Function(int index, NamedType argument) getParameterName = + _getArgumentName, +}) { + final _SwiftFunctionComponents components = _SwiftFunctionComponents( + name: name, + parameters: parameters, + returnType: returnType, + swiftFunction: swiftFunction, + ); + final String returnTypeString = + returnType.isVoid ? 'Void' : _nullsafeSwiftTypeForDartType(returnType); + + final Iterable types = + parameters.map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); + final Iterable labels = indexMap(components.arguments, + (int index, _SwiftFunctionArgument argument) { + return argument.label ?? _getArgumentName(index, argument.namedType); + }); + final Iterable names = indexMap(parameters, getParameterName); + final String parameterSignature = + map3(types, labels, names, (String type, String label, String name) { + return '${label != name ? '$label ' : ''}$name: $type'; + }).join(', '); + + if (isAsynchronous) { + if (parameters.isEmpty) { + return 'func ${components.name}(completion: @escaping (Result<$returnTypeString, $errorTypeName>) -> Void)'; + } else { + return 'func ${components.name}($parameterSignature, completion: @escaping (Result<$returnTypeString, $errorTypeName>) -> Void)'; + } } else { - final Iterable argTypes = func.parameters - .map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); - final Iterable argLabels = indexMap(components.arguments, - (int index, _SwiftFunctionArgument argument) { - return argument.label ?? _getArgumentName(index, argument.namedType); - }); - final Iterable argNames = - indexMap(func.parameters, _getSafeArgumentName); - final String argsSignature = map3(argTypes, argLabels, argNames, - (String type, String label, String name) => '$label $name: $type') - .join(', '); - return 'func ${components.name}($argsSignature, completion: @escaping (Result<$returnType, FlutterError>) -> Void)'; + if (returnType.isVoid) { + return 'func ${components.name}($parameterSignature) throws'; + } else { + return 'func ${components.name}($parameterSignature) throws -> $returnTypeString'; + } } } @@ -954,45 +1041,40 @@ class _SwiftFunctionArgument { /// The [returnType] is the return type of the function. /// The [method] is the method that this function signature is generated from. class _SwiftFunctionComponents { - _SwiftFunctionComponents._({ - required this.name, - required this.arguments, - required this.returnType, - required this.method, - }); - /// Constructor that generates a [_SwiftFunctionComponents] from a [Method]. - factory _SwiftFunctionComponents.fromMethod(Method method) { - if (method.swiftFunction.isEmpty) { + factory _SwiftFunctionComponents({ + required String name, + required Iterable parameters, + required TypeDeclaration returnType, + String? swiftFunction, + }) { + if (swiftFunction == null || swiftFunction.isEmpty) { return _SwiftFunctionComponents._( - name: method.name, - returnType: method.returnType, - arguments: method.parameters + name: name, + returnType: returnType, + arguments: parameters .map((NamedType field) => _SwiftFunctionArgument( name: field.name, type: field.type, namedType: field, )) .toList(), - method: method, ); } - final String argsExtractor = - repeat(r'(\w+):', method.parameters.length).join(); + final String argsExtractor = repeat(r'(\w+):', parameters.length).join(); final RegExp signatureRegex = RegExp(r'(\w+) *\(' + argsExtractor + r'\)'); - final RegExpMatch match = signatureRegex.firstMatch(method.swiftFunction)!; + final RegExpMatch match = signatureRegex.firstMatch(swiftFunction)!; final Iterable labels = match - .groups(List.generate( - method.parameters.length, (int index) => index + 2)) + .groups(List.generate(parameters.length, (int index) => index + 2)) .whereType(); return _SwiftFunctionComponents._( name: match.group(1)!, - returnType: method.returnType, + returnType: returnType, arguments: map2( - method.parameters, + parameters, labels, (NamedType field, String label) => _SwiftFunctionArgument( name: field.name, @@ -1001,12 +1083,16 @@ class _SwiftFunctionComponents { namedType: field, ), ).toList(), - method: method, ); } + _SwiftFunctionComponents._({ + required this.name, + required this.arguments, + required this.returnType, + }); + final String name; final List<_SwiftFunctionArgument> arguments; final TypeDeclaration returnType; - final Method method; } diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart index 9d571688a093..3fbac83fff8b 100644 --- a/packages/pigeon/pigeons/core_tests.dart +++ b/packages/pigeon/pigeons/core_tests.dart @@ -48,6 +48,7 @@ class AllTypes { } /// A class containing all supported nullable types. +@SwiftClass() class AllNullableTypes { AllNullableTypes( this.aNullableBool, @@ -66,6 +67,51 @@ class AllNullableTypes { this.aNullableEnum, this.aNullableString, this.aNullableObject, + this.allNullableTypes, + ); + + bool? aNullableBool; + int? aNullableInt; + int? aNullableInt64; + double? aNullableDouble; + Uint8List? aNullableByteArray; + Int32List? aNullable4ByteArray; + Int64List? aNullable8ByteArray; + Float64List? aNullableFloatArray; + // ignore: always_specify_types, strict_raw_type + List? aNullableList; + // ignore: always_specify_types, strict_raw_type + Map? aNullableMap; + List?>? nullableNestedList; + Map? nullableMapWithAnnotations; + Map? nullableMapWithObject; + AnEnum? aNullableEnum; + String? aNullableString; + Object? aNullableObject; + AllNullableTypes? allNullableTypes; +} + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +class AllNullableTypesWithoutRecursion { + AllNullableTypesWithoutRecursion( + this.aNullableBool, + this.aNullableInt, + this.aNullableInt64, + this.aNullableDouble, + this.aNullableByteArray, + this.aNullable4ByteArray, + this.aNullable8ByteArray, + this.aNullableFloatArray, + this.aNullableList, + this.aNullableMap, + this.nullableNestedList, + this.nullableMapWithAnnotations, + this.nullableMapWithObject, + this.aNullableEnum, + this.aNullableString, + this.aNullableObject, ); bool? aNullableBool; @@ -94,8 +140,10 @@ class AllNullableTypes { /// `AllNullableTypes` is non-nullable here as it is easier to instantiate /// than `AllTypes` when testing doesn't require both (ie. testing null classes). class AllClassesWrapper { - AllClassesWrapper(this.allNullableTypes, this.allTypes); + AllClassesWrapper(this.allNullableTypes, + this.allNullableTypesWithoutRecursion, this.allTypes); AllNullableTypes allNullableTypes; + AllNullableTypesWithoutRecursion? allNullableTypesWithoutRecursion; AllTypes? allTypes; } @@ -195,6 +243,12 @@ abstract class HostIntegrationCoreApi { @SwiftFunction('echo(_:)') AllNullableTypes? echoAllNullableTypes(AllNullableTypes? everything); + /// Returns the passed object, to test serialization and deserialization. + @ObjCSelector('echoAllNullableTypesWithoutRecursion:') + @SwiftFunction('echo(_:)') + AllNullableTypesWithoutRecursion? echoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything); + /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. @ObjCSelector('extractNestedNullableStringFrom:') @@ -213,6 +267,13 @@ abstract class HostIntegrationCoreApi { AllNullableTypes sendMultipleNullableTypes( bool? aNullableBool, int? aNullableInt, String? aNullableString); + /// Returns passed in arguments of multiple types. + @ObjCSelector('sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:') + @SwiftFunction( + 'sendMultipleNullableTypesWithoutRecursion(aBool:anInt:aString:)') + AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + bool? aNullableBool, int? aNullableInt, String? aNullableString); + /// Returns passed in int. @ObjCSelector('echoNullableInt:') @SwiftFunction('echo(_:)') @@ -353,6 +414,14 @@ abstract class HostIntegrationCoreApi { AllNullableTypes? echoAsyncNullableAllNullableTypes( AllNullableTypes? everything); + /// Returns the passed object, to test serialization and deserialization. + @async + @ObjCSelector('echoAsyncNullableAllNullableTypesWithoutRecursion:') + @SwiftFunction('echoAsync(_:)') + AllNullableTypesWithoutRecursion? + echoAsyncNullableAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything); + /// Returns passed in int asynchronously. @async @ObjCSelector('echoAsyncNullableInt:') @@ -435,6 +504,22 @@ abstract class HostIntegrationCoreApi { AllNullableTypes callFlutterSendMultipleNullableTypes( bool? aNullableBool, int? aNullableInt, String? aNullableString); + @async + @ObjCSelector('callFlutterEchoAllNullableTypesWithoutRecursion:') + @SwiftFunction('callFlutterEcho(_:)') + AllNullableTypesWithoutRecursion? + callFlutterEchoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything); + + @async + @ObjCSelector( + 'callFlutterSendMultipleNullableTypesWithoutRecursionABool:anInt:aString:') + @SwiftFunction( + 'callFlutterSendMultipleNullableTypesWithoutRecursion(aBool:anInt:aString:)') + AllNullableTypesWithoutRecursion + callFlutterSendMultipleNullableTypesWithoutRecursion( + bool? aNullableBool, int? aNullableInt, String? aNullableString); + @async @ObjCSelector('callFlutterEchoBool:') @SwiftFunction('callFlutterEcho(_:)') @@ -549,6 +634,21 @@ abstract class FlutterIntegrationCoreApi { AllNullableTypes sendMultipleNullableTypes( bool? aNullableBool, int? aNullableInt, String? aNullableString); + /// Returns the passed object, to test serialization and deserialization. + @ObjCSelector('echoAllNullableTypesWithoutRecursion:') + @SwiftFunction('echoNullable(_:)') + AllNullableTypesWithoutRecursion? echoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything); + + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + @ObjCSelector('sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:') + @SwiftFunction( + 'sendMultipleNullableTypesWithoutRecursion(aBool:anInt:aString:)') + AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + bool? aNullableBool, int? aNullableInt, String? aNullableString); + // ========== Non-nullable argument/return type tests ========== /// Returns the passed boolean, to test serialization and deserialization. diff --git a/packages/pigeon/pigeons/proxy_api_tests.dart b/packages/pigeon/pigeons/proxy_api_tests.dart new file mode 100644 index 000000000000..699e5dfaed86 --- /dev/null +++ b/packages/pigeon/pigeons/proxy_api_tests.dart @@ -0,0 +1,469 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +enum ProxyApiTestEnum { + one, + two, + three, +} + +/// The core ProxyApi test class that each supported host language must +/// implement in platform_tests integration tests. +@ProxyApi() +abstract class ProxyApiTestClass extends ProxyApiSuperClass + implements ProxyApiInterface { + ProxyApiTestClass( + // ignore: avoid_unused_constructor_parameters + bool boolParam, + // ignore: avoid_unused_constructor_parameters + int intParam, + // ignore: avoid_unused_constructor_parameters + double doubleParam, + // ignore: avoid_unused_constructor_parameters + String stringParam, + // ignore: avoid_unused_constructor_parameters + Uint8List aUint8ListParam, + // ignore: avoid_unused_constructor_parameters + List listParam, + // ignore: avoid_unused_constructor_parameters + Map mapParam, + // ignore: avoid_unused_constructor_parameters + ProxyApiTestEnum enumParam, + // ignore: avoid_unused_constructor_parameters + ProxyApiSuperClass proxyApiParam, + // ignore: avoid_unused_constructor_parameters + bool? nullableBoolParam, + // ignore: avoid_unused_constructor_parameters + int? nullableIntParam, + // ignore: avoid_unused_constructor_parameters + double? nullableDoubleParam, + // ignore: avoid_unused_constructor_parameters + String? nullableStringParam, + // ignore: avoid_unused_constructor_parameters + Uint8List? nullableUint8ListParam, + // ignore: avoid_unused_constructor_parameters + List? nullableListParam, + // ignore: avoid_unused_constructor_parameters + Map? nullableMapParam, + // ignore: avoid_unused_constructor_parameters + ProxyApiTestEnum? nullableEnumParam, + // ignore: avoid_unused_constructor_parameters + ProxyApiSuperClass? nullableProxyApiParam, + ); + + late bool aBool; + late int anInt; + late double aDouble; + late String aString; + late Uint8List aUint8List; + late List aList; + late Map aMap; + late ProxyApiTestEnum anEnum; + late ProxyApiSuperClass aProxyApi; + + late bool? aNullableBool; + late int? aNullableInt; + late double? aNullableDouble; + late String? aNullableString; + late Uint8List? aNullableUint8List; + late List? aNullableList; + late Map? aNullableMap; + late ProxyApiTestEnum? aNullableEnum; + late ProxyApiSuperClass? aNullableProxyApi; + + @attached + late ProxyApiSuperClass attachedField; + + @static + late ProxyApiSuperClass staticAttachedField; + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic calling. + late void Function()? flutterNoop; + + /// Responds with an error from an async function returning a value. + late Object? Function()? flutterThrowError; + + /// Responds with an error from an async void function. + late void Function()? flutterThrowErrorFromVoid; + + // ========== Non-nullable argument/return type tests ========== + + /// Returns the passed boolean, to test serialization and deserialization. + late bool Function(bool aBool)? flutterEchoBool; + + /// Returns the passed int, to test serialization and deserialization. + late int Function(int anInt)? flutterEchoInt; + + /// Returns the passed double, to test serialization and deserialization. + late double Function(double aDouble)? flutterEchoDouble; + + /// Returns the passed string, to test serialization and deserialization. + late String Function(String aString)? flutterEchoString; + + /// Returns the passed byte list, to test serialization and deserialization. + late Uint8List Function(Uint8List aList)? flutterEchoUint8List; + + /// Returns the passed list, to test serialization and deserialization. + late List Function(List aList)? flutterEchoList; + + /// Returns the passed list with ProxyApis, to test serialization and + /// deserialization. + late List Function(List aList)? + flutterEchoProxyApiList; + + /// Returns the passed map, to test serialization and deserialization. + late Map Function(Map aMap)? + flutterEchoMap; + + /// Returns the passed map with ProxyApis, to test serialization and + /// deserialization. + late Map Function( + Map aMap)? flutterEchoProxyApiMap; + + /// Returns the passed enum to test serialization and deserialization. + late ProxyApiTestEnum Function(ProxyApiTestEnum anEnum)? flutterEchoEnum; + + /// Returns the passed ProxyApi to test serialization and deserialization. + late ProxyApiSuperClass Function(ProxyApiSuperClass aProxyApi)? + flutterEchoProxyApi; + + // ========== Nullable argument/return type tests ========== + + /// Returns the passed boolean, to test serialization and deserialization. + late bool? Function(bool? aBool)? flutterEchoNullableBool; + + /// Returns the passed int, to test serialization and deserialization. + late int? Function(int? anInt)? flutterEchoNullableInt; + + /// Returns the passed double, to test serialization and deserialization. + late double? Function(double? aDouble)? flutterEchoNullableDouble; + + /// Returns the passed string, to test serialization and deserialization. + late String? Function(String? aString)? flutterEchoNullableString; + + /// Returns the passed byte list, to test serialization and deserialization. + late Uint8List? Function(Uint8List? aList)? flutterEchoNullableUint8List; + + /// Returns the passed list, to test serialization and deserialization. + late List? Function(List? aList)? flutterEchoNullableList; + + /// Returns the passed map, to test serialization and deserialization. + late Map? Function(Map? aMap)? + flutterEchoNullableMap; + + /// Returns the passed enum to test serialization and deserialization. + late ProxyApiTestEnum? Function(ProxyApiTestEnum? anEnum)? + flutterEchoNullableEnum; + + /// Returns the passed ProxyApi to test serialization and deserialization. + late ProxyApiSuperClass? Function(ProxyApiSuperClass? aProxyApi)? + flutterEchoNullableProxyApi; + + // ========== Async tests ========== + // These are minimal since async FlutterApi only changes Dart generation. + // Currently they aren't integration tested, but having them here ensures + // analysis coverage. + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic asynchronous calling. + @async + late void Function()? flutterNoopAsync; + + /// Returns the passed in generic Object asynchronously. + @async + late String Function(String aString)? flutterEchoAsyncString; + + // ========== Synchronous host method tests ========== + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic calling. + void noop(); + + /// Returns an error, to test error handling. + Object? throwError(); + + /// Returns an error from a void function, to test error handling. + void throwErrorFromVoid(); + + /// Returns a Flutter error, to test error handling. + Object? throwFlutterError(); + + /// Returns passed in int. + int echoInt(int anInt); + + /// Returns passed in double. + double echoDouble(double aDouble); + + /// Returns the passed in boolean. + bool echoBool(bool aBool); + + /// Returns the passed in string. + String echoString(String aString); + + /// Returns the passed in Uint8List. + Uint8List echoUint8List(Uint8List aUint8List); + + /// Returns the passed in generic Object. + Object echoObject(Object anObject); + + /// Returns the passed list, to test serialization and deserialization. + List echoList(List aList); + + /// Returns the passed list with ProxyApis, to test serialization and + /// deserialization. + List echoProxyApiList( + List aList, + ); + + /// Returns the passed map, to test serialization and deserialization. + Map echoMap(Map aMap); + + /// Returns the passed map with ProxyApis, to test serialization and + /// deserialization. + Map echoProxyApiMap( + Map aMap, + ); + + /// Returns the passed enum to test serialization and deserialization. + ProxyApiTestEnum echoEnum(ProxyApiTestEnum anEnum); + + /// Returns the passed ProxyApi to test serialization and deserialization. + ProxyApiSuperClass echoProxyApi(ProxyApiSuperClass aProxyApi); + + // ========== Synchronous host nullable method tests ========== + + /// Returns passed in int. + int? echoNullableInt(int? aNullableInt); + + /// Returns passed in double. + double? echoNullableDouble(double? aNullableDouble); + + /// Returns the passed in boolean. + bool? echoNullableBool(bool? aNullableBool); + + /// Returns the passed in string. + String? echoNullableString(String? aNullableString); + + /// Returns the passed in Uint8List. + Uint8List? echoNullableUint8List(Uint8List? aNullableUint8List); + + /// Returns the passed in generic Object. + Object? echoNullableObject(Object? aNullableObject); + + /// Returns the passed list, to test serialization and deserialization. + List? echoNullableList(List? aNullableList); + + /// Returns the passed map, to test serialization and deserialization. + Map? echoNullableMap(Map? aNullableMap); + + ProxyApiTestEnum? echoNullableEnum(ProxyApiTestEnum? aNullableEnum); + + /// Returns the passed ProxyApi to test serialization and deserialization. + ProxyApiSuperClass? echoNullableProxyApi( + ProxyApiSuperClass? aNullableProxyApi, + ); + + // ========== Asynchronous method tests ========== + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic asynchronous calling. + @async + void noopAsync(); + + /// Returns passed in int asynchronously. + @async + int echoAsyncInt(int anInt); + + /// Returns passed in double asynchronously. + @async + double echoAsyncDouble(double aDouble); + + /// Returns the passed in boolean asynchronously. + @async + bool echoAsyncBool(bool aBool); + + /// Returns the passed string asynchronously. + @async + String echoAsyncString(String aString); + + /// Returns the passed in Uint8List asynchronously. + @async + Uint8List echoAsyncUint8List(Uint8List aUint8List); + + /// Returns the passed in generic Object asynchronously. + @async + Object echoAsyncObject(Object anObject); + + /// Returns the passed list, to test asynchronous serialization and deserialization. + @async + List echoAsyncList(List aList); + + /// Returns the passed map, to test asynchronous serialization and deserialization. + @async + Map echoAsyncMap(Map aMap); + + /// Returns the passed enum, to test asynchronous serialization and deserialization. + @async + ProxyApiTestEnum echoAsyncEnum(ProxyApiTestEnum anEnum); + + /// Responds with an error from an async function returning a value. + @async + Object? throwAsyncError(); + + /// Responds with an error from an async void function. + @async + void throwAsyncErrorFromVoid(); + + /// Responds with a Flutter error from an async function returning a value. + @async + Object? throwAsyncFlutterError(); + + /// Returns passed in int asynchronously. + @async + int? echoAsyncNullableInt(int? anInt); + + /// Returns passed in double asynchronously. + @async + double? echoAsyncNullableDouble(double? aDouble); + + /// Returns the passed in boolean asynchronously. + @async + bool? echoAsyncNullableBool(bool? aBool); + + /// Returns the passed string asynchronously. + @async + String? echoAsyncNullableString(String? aString); + + /// Returns the passed in Uint8List asynchronously. + @async + Uint8List? echoAsyncNullableUint8List(Uint8List? aUint8List); + + /// Returns the passed in generic Object asynchronously. + @async + Object? echoAsyncNullableObject(Object? anObject); + + /// Returns the passed list, to test asynchronous serialization and deserialization. + @async + List? echoAsyncNullableList(List? aList); + + /// Returns the passed map, to test asynchronous serialization and deserialization. + @async + Map? echoAsyncNullableMap(Map? aMap); + + /// Returns the passed enum, to test asynchronous serialization and deserialization. + @async + ProxyApiTestEnum? echoAsyncNullableEnum(ProxyApiTestEnum? anEnum); + + // ========== Static method test ========== + + @static + void staticNoop(); + + @static + String echoStaticString(String aString); + + @static + @async + void staticAsyncNoop(); + + // ========== Flutter methods test wrappers ========== + + @async + void callFlutterNoop(); + + @async + Object? callFlutterThrowError(); + + @async + void callFlutterThrowErrorFromVoid(); + + @async + bool callFlutterEchoBool(bool aBool); + + @async + int callFlutterEchoInt(int anInt); + + @async + double callFlutterEchoDouble(double aDouble); + + @async + String callFlutterEchoString(String aString); + + @async + Uint8List callFlutterEchoUint8List(Uint8List aUint8List); + + @async + List callFlutterEchoList(List aList); + + @async + List callFlutterEchoProxyApiList( + List aList); + + @async + Map callFlutterEchoMap(Map aMap); + + @async + Map callFlutterEchoProxyApiMap( + Map aMap); + + @async + ProxyApiTestEnum callFlutterEchoEnum(ProxyApiTestEnum anEnum); + + @async + ProxyApiSuperClass callFlutterEchoProxyApi(ProxyApiSuperClass aProxyApi); + + @async + bool? callFlutterEchoNullableBool(bool? aBool); + + @async + int? callFlutterEchoNullableInt(int? anInt); + + @async + double? callFlutterEchoNullableDouble(double? aDouble); + + @async + String? callFlutterEchoNullableString(String? aString); + + @async + Uint8List? callFlutterEchoNullableUint8List(Uint8List? aUint8List); + + @async + List? callFlutterEchoNullableList(List? aList); + + @async + Map? callFlutterEchoNullableMap( + Map? aMap, + ); + + @async + ProxyApiTestEnum? callFlutterEchoNullableEnum(ProxyApiTestEnum? anEnum); + + @async + ProxyApiSuperClass? callFlutterEchoNullableProxyApi( + ProxyApiSuperClass? aProxyApi, + ); + + @async + void callFlutterNoopAsync(); + + @async + String callFlutterEchoAsyncString(String aString); +} + +/// ProxyApi to serve as a super class to the core ProxyApi class. +@ProxyApi() +abstract class ProxyApiSuperClass { + ProxyApiSuperClass(); + + void aSuperMethod(); +} + +/// ProxyApi to serve as an interface to the core ProxyApi class. +@ProxyApi() +abstract class ProxyApiInterface { + late void Function()? anInterfaceMethod; +} diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java index a99aa2ef913f..7a44f6a88834 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import com.example.alternate_language_test_plugin.CoreTests.AllClassesWrapper; import com.example.alternate_language_test_plugin.CoreTests.AllNullableTypes; +import com.example.alternate_language_test_plugin.CoreTests.AllNullableTypesWithoutRecursion; import com.example.alternate_language_test_plugin.CoreTests.AllTypes; import com.example.alternate_language_test_plugin.CoreTests.AnEnum; import com.example.alternate_language_test_plugin.CoreTests.FlutterIntegrationCoreApi; @@ -47,6 +48,12 @@ public void noop() {} return everything; } + @Override + public @Nullable AllNullableTypesWithoutRecursion echoAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything) { + return everything; + } + @Override public @Nullable Object throwError() { throw new RuntimeException("An error"); @@ -153,6 +160,20 @@ public void throwErrorFromVoid() { return someThings; } + @Override + public @NonNull AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + @Nullable Boolean aNullableBool, + @Nullable Long aNullableInt, + @Nullable String aNullableString) { + AllNullableTypesWithoutRecursion someThings = + new AllNullableTypesWithoutRecursion.Builder() + .setANullableBool(aNullableBool) + .setANullableInt(aNullableInt) + .setANullableString(aNullableString) + .build(); + return someThings; + } + @Override public @Nullable Long echoNullableInt(@Nullable Long aNullableInt) { return aNullableInt; @@ -239,6 +260,13 @@ public void echoAsyncNullableAllNullableTypes( result.success(everything); } + @Override + public void echoAsyncNullableAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything, + @NonNull NullableResult result) { + result.success(everything); + } + @Override public void echoAsyncInt(@NonNull Long anInt, @NonNull Result result) { result.success(anInt); @@ -374,6 +402,23 @@ public void callFlutterSendMultipleNullableTypes( flutterApi.sendMultipleNullableTypes(aNullableBool, aNullableInt, aNullableString, result); } + @Override + public void callFlutterEchoAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything, + @NonNull NullableResult result) { + flutterApi.echoAllNullableTypesWithoutRecursion(everything, result); + } + + @Override + public void callFlutterSendMultipleNullableTypesWithoutRecursion( + @Nullable Boolean aNullableBool, + @Nullable Long aNullableInt, + @Nullable String aNullableString, + @NonNull Result result) { + flutterApi.sendMultipleNullableTypesWithoutRecursion( + aNullableBool, aNullableInt, aNullableString, result); + } + @Override public void callFlutterEchoBool(@NonNull Boolean aBool, @NonNull Result result) { flutterApi.echoBool(aBool, result); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java index 0917a8cfa791..93dad3828781 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java @@ -612,6 +612,420 @@ public void setANullableObject(@Nullable Object setterArg) { this.aNullableObject = setterArg; } + private @Nullable AllNullableTypes allNullableTypes; + + public @Nullable AllNullableTypes getAllNullableTypes() { + return allNullableTypes; + } + + public void setAllNullableTypes(@Nullable AllNullableTypes setterArg) { + this.allNullableTypes = setterArg; + } + + public static final class Builder { + + private @Nullable Boolean aNullableBool; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableBool(@Nullable Boolean setterArg) { + this.aNullableBool = setterArg; + return this; + } + + private @Nullable Long aNullableInt; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableInt(@Nullable Long setterArg) { + this.aNullableInt = setterArg; + return this; + } + + private @Nullable Long aNullableInt64; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableInt64(@Nullable Long setterArg) { + this.aNullableInt64 = setterArg; + return this; + } + + private @Nullable Double aNullableDouble; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableDouble(@Nullable Double setterArg) { + this.aNullableDouble = setterArg; + return this; + } + + private @Nullable byte[] aNullableByteArray; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableByteArray(@Nullable byte[] setterArg) { + this.aNullableByteArray = setterArg; + return this; + } + + private @Nullable int[] aNullable4ByteArray; + + @CanIgnoreReturnValue + public @NonNull Builder setANullable4ByteArray(@Nullable int[] setterArg) { + this.aNullable4ByteArray = setterArg; + return this; + } + + private @Nullable long[] aNullable8ByteArray; + + @CanIgnoreReturnValue + public @NonNull Builder setANullable8ByteArray(@Nullable long[] setterArg) { + this.aNullable8ByteArray = setterArg; + return this; + } + + private @Nullable double[] aNullableFloatArray; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableFloatArray(@Nullable double[] setterArg) { + this.aNullableFloatArray = setterArg; + return this; + } + + private @Nullable List aNullableList; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableList(@Nullable List setterArg) { + this.aNullableList = setterArg; + return this; + } + + private @Nullable Map aNullableMap; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableMap(@Nullable Map setterArg) { + this.aNullableMap = setterArg; + return this; + } + + private @Nullable List> nullableNestedList; + + @CanIgnoreReturnValue + public @NonNull Builder setNullableNestedList(@Nullable List> setterArg) { + this.nullableNestedList = setterArg; + return this; + } + + private @Nullable Map nullableMapWithAnnotations; + + @CanIgnoreReturnValue + public @NonNull Builder setNullableMapWithAnnotations( + @Nullable Map setterArg) { + this.nullableMapWithAnnotations = setterArg; + return this; + } + + private @Nullable Map nullableMapWithObject; + + @CanIgnoreReturnValue + public @NonNull Builder setNullableMapWithObject(@Nullable Map setterArg) { + this.nullableMapWithObject = setterArg; + return this; + } + + private @Nullable AnEnum aNullableEnum; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableEnum(@Nullable AnEnum setterArg) { + this.aNullableEnum = setterArg; + return this; + } + + private @Nullable String aNullableString; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableString(@Nullable String setterArg) { + this.aNullableString = setterArg; + return this; + } + + private @Nullable Object aNullableObject; + + @CanIgnoreReturnValue + public @NonNull Builder setANullableObject(@Nullable Object setterArg) { + this.aNullableObject = setterArg; + return this; + } + + private @Nullable AllNullableTypes allNullableTypes; + + @CanIgnoreReturnValue + public @NonNull Builder setAllNullableTypes(@Nullable AllNullableTypes setterArg) { + this.allNullableTypes = setterArg; + return this; + } + + public @NonNull AllNullableTypes build() { + AllNullableTypes pigeonReturn = new AllNullableTypes(); + pigeonReturn.setANullableBool(aNullableBool); + pigeonReturn.setANullableInt(aNullableInt); + pigeonReturn.setANullableInt64(aNullableInt64); + pigeonReturn.setANullableDouble(aNullableDouble); + pigeonReturn.setANullableByteArray(aNullableByteArray); + pigeonReturn.setANullable4ByteArray(aNullable4ByteArray); + pigeonReturn.setANullable8ByteArray(aNullable8ByteArray); + pigeonReturn.setANullableFloatArray(aNullableFloatArray); + pigeonReturn.setANullableList(aNullableList); + pigeonReturn.setANullableMap(aNullableMap); + pigeonReturn.setNullableNestedList(nullableNestedList); + pigeonReturn.setNullableMapWithAnnotations(nullableMapWithAnnotations); + pigeonReturn.setNullableMapWithObject(nullableMapWithObject); + pigeonReturn.setANullableEnum(aNullableEnum); + pigeonReturn.setANullableString(aNullableString); + pigeonReturn.setANullableObject(aNullableObject); + pigeonReturn.setAllNullableTypes(allNullableTypes); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(17); + toListResult.add(aNullableBool); + toListResult.add(aNullableInt); + toListResult.add(aNullableInt64); + toListResult.add(aNullableDouble); + toListResult.add(aNullableByteArray); + toListResult.add(aNullable4ByteArray); + toListResult.add(aNullable8ByteArray); + toListResult.add(aNullableFloatArray); + toListResult.add(aNullableList); + toListResult.add(aNullableMap); + toListResult.add(nullableNestedList); + toListResult.add(nullableMapWithAnnotations); + toListResult.add(nullableMapWithObject); + toListResult.add(aNullableEnum == null ? null : aNullableEnum.index); + toListResult.add(aNullableString); + toListResult.add(aNullableObject); + toListResult.add((allNullableTypes == null) ? null : allNullableTypes.toList()); + return toListResult; + } + + static @NonNull AllNullableTypes fromList(@NonNull ArrayList list) { + AllNullableTypes pigeonResult = new AllNullableTypes(); + Object aNullableBool = list.get(0); + pigeonResult.setANullableBool((Boolean) aNullableBool); + Object aNullableInt = list.get(1); + pigeonResult.setANullableInt( + (aNullableInt == null) + ? null + : ((aNullableInt instanceof Integer) ? (Integer) aNullableInt : (Long) aNullableInt)); + Object aNullableInt64 = list.get(2); + pigeonResult.setANullableInt64( + (aNullableInt64 == null) + ? null + : ((aNullableInt64 instanceof Integer) + ? (Integer) aNullableInt64 + : (Long) aNullableInt64)); + Object aNullableDouble = list.get(3); + pigeonResult.setANullableDouble((Double) aNullableDouble); + Object aNullableByteArray = list.get(4); + pigeonResult.setANullableByteArray((byte[]) aNullableByteArray); + Object aNullable4ByteArray = list.get(5); + pigeonResult.setANullable4ByteArray((int[]) aNullable4ByteArray); + Object aNullable8ByteArray = list.get(6); + pigeonResult.setANullable8ByteArray((long[]) aNullable8ByteArray); + Object aNullableFloatArray = list.get(7); + pigeonResult.setANullableFloatArray((double[]) aNullableFloatArray); + Object aNullableList = list.get(8); + pigeonResult.setANullableList((List) aNullableList); + Object aNullableMap = list.get(9); + pigeonResult.setANullableMap((Map) aNullableMap); + Object nullableNestedList = list.get(10); + pigeonResult.setNullableNestedList((List>) nullableNestedList); + Object nullableMapWithAnnotations = list.get(11); + pigeonResult.setNullableMapWithAnnotations((Map) nullableMapWithAnnotations); + Object nullableMapWithObject = list.get(12); + pigeonResult.setNullableMapWithObject((Map) nullableMapWithObject); + Object aNullableEnum = list.get(13); + pigeonResult.setANullableEnum( + aNullableEnum == null ? null : AnEnum.values()[(int) aNullableEnum]); + Object aNullableString = list.get(14); + pigeonResult.setANullableString((String) aNullableString); + Object aNullableObject = list.get(15); + pigeonResult.setANullableObject(aNullableObject); + Object allNullableTypes = list.get(16); + pigeonResult.setAllNullableTypes( + (allNullableTypes == null) + ? null + : AllNullableTypes.fromList((ArrayList) allNullableTypes)); + return pigeonResult; + } + } + + /** + * The primary purpose for this class is to ensure coverage of Swift structs with nullable items, + * as the primary [AllNullableTypes] class is being used to test Swift classes. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class AllNullableTypesWithoutRecursion { + private @Nullable Boolean aNullableBool; + + public @Nullable Boolean getANullableBool() { + return aNullableBool; + } + + public void setANullableBool(@Nullable Boolean setterArg) { + this.aNullableBool = setterArg; + } + + private @Nullable Long aNullableInt; + + public @Nullable Long getANullableInt() { + return aNullableInt; + } + + public void setANullableInt(@Nullable Long setterArg) { + this.aNullableInt = setterArg; + } + + private @Nullable Long aNullableInt64; + + public @Nullable Long getANullableInt64() { + return aNullableInt64; + } + + public void setANullableInt64(@Nullable Long setterArg) { + this.aNullableInt64 = setterArg; + } + + private @Nullable Double aNullableDouble; + + public @Nullable Double getANullableDouble() { + return aNullableDouble; + } + + public void setANullableDouble(@Nullable Double setterArg) { + this.aNullableDouble = setterArg; + } + + private @Nullable byte[] aNullableByteArray; + + public @Nullable byte[] getANullableByteArray() { + return aNullableByteArray; + } + + public void setANullableByteArray(@Nullable byte[] setterArg) { + this.aNullableByteArray = setterArg; + } + + private @Nullable int[] aNullable4ByteArray; + + public @Nullable int[] getANullable4ByteArray() { + return aNullable4ByteArray; + } + + public void setANullable4ByteArray(@Nullable int[] setterArg) { + this.aNullable4ByteArray = setterArg; + } + + private @Nullable long[] aNullable8ByteArray; + + public @Nullable long[] getANullable8ByteArray() { + return aNullable8ByteArray; + } + + public void setANullable8ByteArray(@Nullable long[] setterArg) { + this.aNullable8ByteArray = setterArg; + } + + private @Nullable double[] aNullableFloatArray; + + public @Nullable double[] getANullableFloatArray() { + return aNullableFloatArray; + } + + public void setANullableFloatArray(@Nullable double[] setterArg) { + this.aNullableFloatArray = setterArg; + } + + private @Nullable List aNullableList; + + public @Nullable List getANullableList() { + return aNullableList; + } + + public void setANullableList(@Nullable List setterArg) { + this.aNullableList = setterArg; + } + + private @Nullable Map aNullableMap; + + public @Nullable Map getANullableMap() { + return aNullableMap; + } + + public void setANullableMap(@Nullable Map setterArg) { + this.aNullableMap = setterArg; + } + + private @Nullable List> nullableNestedList; + + public @Nullable List> getNullableNestedList() { + return nullableNestedList; + } + + public void setNullableNestedList(@Nullable List> setterArg) { + this.nullableNestedList = setterArg; + } + + private @Nullable Map nullableMapWithAnnotations; + + public @Nullable Map getNullableMapWithAnnotations() { + return nullableMapWithAnnotations; + } + + public void setNullableMapWithAnnotations(@Nullable Map setterArg) { + this.nullableMapWithAnnotations = setterArg; + } + + private @Nullable Map nullableMapWithObject; + + public @Nullable Map getNullableMapWithObject() { + return nullableMapWithObject; + } + + public void setNullableMapWithObject(@Nullable Map setterArg) { + this.nullableMapWithObject = setterArg; + } + + private @Nullable AnEnum aNullableEnum; + + public @Nullable AnEnum getANullableEnum() { + return aNullableEnum; + } + + public void setANullableEnum(@Nullable AnEnum setterArg) { + this.aNullableEnum = setterArg; + } + + private @Nullable String aNullableString; + + public @Nullable String getANullableString() { + return aNullableString; + } + + public void setANullableString(@Nullable String setterArg) { + this.aNullableString = setterArg; + } + + private @Nullable Object aNullableObject; + + public @Nullable Object getANullableObject() { + return aNullableObject; + } + + public void setANullableObject(@Nullable Object setterArg) { + this.aNullableObject = setterArg; + } + public static final class Builder { private @Nullable Boolean aNullableBool; @@ -743,8 +1157,8 @@ public static final class Builder { return this; } - public @NonNull AllNullableTypes build() { - AllNullableTypes pigeonReturn = new AllNullableTypes(); + public @NonNull AllNullableTypesWithoutRecursion build() { + AllNullableTypesWithoutRecursion pigeonReturn = new AllNullableTypesWithoutRecursion(); pigeonReturn.setANullableBool(aNullableBool); pigeonReturn.setANullableInt(aNullableInt); pigeonReturn.setANullableInt64(aNullableInt64); @@ -787,8 +1201,8 @@ ArrayList toList() { return toListResult; } - static @NonNull AllNullableTypes fromList(@NonNull ArrayList list) { - AllNullableTypes pigeonResult = new AllNullableTypes(); + static @NonNull AllNullableTypesWithoutRecursion fromList(@NonNull ArrayList list) { + AllNullableTypesWithoutRecursion pigeonResult = new AllNullableTypesWithoutRecursion(); Object aNullableBool = list.get(0); pigeonResult.setANullableBool((Boolean) aNullableBool); Object aNullableInt = list.get(1); @@ -857,6 +1271,17 @@ public void setAllNullableTypes(@NonNull AllNullableTypes setterArg) { this.allNullableTypes = setterArg; } + private @Nullable AllNullableTypesWithoutRecursion allNullableTypesWithoutRecursion; + + public @Nullable AllNullableTypesWithoutRecursion getAllNullableTypesWithoutRecursion() { + return allNullableTypesWithoutRecursion; + } + + public void setAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion setterArg) { + this.allNullableTypesWithoutRecursion = setterArg; + } + private @Nullable AllTypes allTypes; public @Nullable AllTypes getAllTypes() { @@ -880,6 +1305,15 @@ public static final class Builder { return this; } + private @Nullable AllNullableTypesWithoutRecursion allNullableTypesWithoutRecursion; + + @CanIgnoreReturnValue + public @NonNull Builder setAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion setterArg) { + this.allNullableTypesWithoutRecursion = setterArg; + return this; + } + private @Nullable AllTypes allTypes; @CanIgnoreReturnValue @@ -891,6 +1325,7 @@ public static final class Builder { public @NonNull AllClassesWrapper build() { AllClassesWrapper pigeonReturn = new AllClassesWrapper(); pigeonReturn.setAllNullableTypes(allNullableTypes); + pigeonReturn.setAllNullableTypesWithoutRecursion(allNullableTypesWithoutRecursion); pigeonReturn.setAllTypes(allTypes); return pigeonReturn; } @@ -898,8 +1333,12 @@ public static final class Builder { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(2); + ArrayList toListResult = new ArrayList(3); toListResult.add((allNullableTypes == null) ? null : allNullableTypes.toList()); + toListResult.add( + (allNullableTypesWithoutRecursion == null) + ? null + : allNullableTypesWithoutRecursion.toList()); toListResult.add((allTypes == null) ? null : allTypes.toList()); return toListResult; } @@ -911,7 +1350,13 @@ ArrayList toList() { (allNullableTypes == null) ? null : AllNullableTypes.fromList((ArrayList) allNullableTypes)); - Object allTypes = list.get(1); + Object allNullableTypesWithoutRecursion = list.get(1); + pigeonResult.setAllNullableTypesWithoutRecursion( + (allNullableTypesWithoutRecursion == null) + ? null + : AllNullableTypesWithoutRecursion.fromList( + (ArrayList) allNullableTypesWithoutRecursion)); + Object allTypes = list.get(2); pigeonResult.setAllTypes( (allTypes == null) ? null : AllTypes.fromList((ArrayList) allTypes)); return pigeonResult; @@ -1004,8 +1449,10 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 129: return AllNullableTypes.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return AllTypes.fromList((ArrayList) readValue(buffer)); + return AllNullableTypesWithoutRecursion.fromList((ArrayList) readValue(buffer)); case (byte) 131: + return AllTypes.fromList((ArrayList) readValue(buffer)); + case (byte) 132: return TestMessage.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -1020,11 +1467,14 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof AllNullableTypes) { stream.write(129); writeValue(stream, ((AllNullableTypes) value).toList()); - } else if (value instanceof AllTypes) { + } else if (value instanceof AllNullableTypesWithoutRecursion) { stream.write(130); + writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList()); + } else if (value instanceof AllTypes) { + stream.write(131); writeValue(stream, ((AllTypes) value).toList()); } else if (value instanceof TestMessage) { - stream.write(131); + stream.write(132); writeValue(stream, ((TestMessage) value).toList()); } else { super.writeValue(stream, value); @@ -1096,6 +1546,10 @@ public interface HostIntegrationCoreApi { /** Returns the passed object, to test serialization and deserialization. */ @Nullable AllNullableTypes echoAllNullableTypes(@Nullable AllNullableTypes everything); + /** Returns the passed object, to test serialization and deserialization. */ + @Nullable + AllNullableTypesWithoutRecursion echoAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything); /** * Returns the inner `aString` value from the wrapped object, to test sending of nested objects. */ @@ -1112,6 +1566,12 @@ AllNullableTypes sendMultipleNullableTypes( @Nullable Boolean aNullableBool, @Nullable Long aNullableInt, @Nullable String aNullableString); + /** Returns passed in arguments of multiple types. */ + @NonNull + AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + @Nullable Boolean aNullableBool, + @Nullable Long aNullableInt, + @Nullable String aNullableString); /** Returns passed in int. */ @Nullable Long echoNullableInt(@Nullable Long aNullableInt); @@ -1180,6 +1640,10 @@ void echoAsyncMap( /** Returns the passed object, to test serialization and deserialization. */ void echoAsyncNullableAllNullableTypes( @Nullable AllNullableTypes everything, @NonNull NullableResult result); + /** Returns the passed object, to test serialization and deserialization. */ + void echoAsyncNullableAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything, + @NonNull NullableResult result); /** Returns passed in int asynchronously. */ void echoAsyncNullableInt(@Nullable Long anInt, @NonNull NullableResult result); /** Returns passed in double asynchronously. */ @@ -1219,6 +1683,16 @@ void callFlutterSendMultipleNullableTypes( @Nullable String aNullableString, @NonNull Result result); + void callFlutterEchoAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everything, + @NonNull NullableResult result); + + void callFlutterSendMultipleNullableTypesWithoutRecursion( + @Nullable Boolean aNullableBool, + @Nullable Long aNullableInt, + @Nullable String aNullableString, + @NonNull Result result); + void callFlutterEchoBool(@NonNull Boolean aBool, @NonNull Result result); void callFlutterEchoInt(@NonNull Long anInt, @NonNull Result result); @@ -1737,6 +2211,33 @@ static void setUp( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + AllNullableTypesWithoutRecursion everythingArg = + (AllNullableTypesWithoutRecursion) args.get(0); + try { + AllNullableTypesWithoutRecursion output = + api.echoAllNullableTypesWithoutRecursion(everythingArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -1818,6 +2319,37 @@ static void setUp( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Boolean aNullableBoolArg = (Boolean) args.get(0); + Number aNullableIntArg = (Number) args.get(1); + String aNullableStringArg = (String) args.get(2); + try { + AllNullableTypesWithoutRecursion output = + api.sendMultipleNullableTypesWithoutRecursion( + aNullableBoolArg, + (aNullableIntArg == null) ? null : aNullableIntArg.longValue(), + aNullableStringArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -2554,6 +3086,39 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableAllNullableTypesWithoutRecursion", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + AllNullableTypesWithoutRecursion everythingArg = + (AllNullableTypesWithoutRecursion) args.get(0); + NullableResult resultCallback = + new NullableResult() { + public void success(AllNullableTypesWithoutRecursion result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.echoAsyncNullableAllNullableTypesWithoutRecursion( + everythingArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -3020,6 +3585,75 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoAllNullableTypesWithoutRecursion", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + AllNullableTypesWithoutRecursion everythingArg = + (AllNullableTypesWithoutRecursion) args.get(0); + NullableResult resultCallback = + new NullableResult() { + public void success(AllNullableTypesWithoutRecursion result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.callFlutterEchoAllNullableTypesWithoutRecursion(everythingArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterSendMultipleNullableTypesWithoutRecursion", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Boolean aNullableBoolArg = (Boolean) args.get(0); + Number aNullableIntArg = (Number) args.get(1); + String aNullableStringArg = (String) args.get(2); + Result resultCallback = + new Result() { + public void success(AllNullableTypesWithoutRecursion result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.callFlutterSendMultipleNullableTypesWithoutRecursion( + aNullableBoolArg, + (aNullableIntArg == null) ? null : aNullableIntArg.longValue(), + aNullableStringArg, + resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -3535,8 +4169,10 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 129: return AllNullableTypes.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return AllTypes.fromList((ArrayList) readValue(buffer)); + return AllNullableTypesWithoutRecursion.fromList((ArrayList) readValue(buffer)); case (byte) 131: + return AllTypes.fromList((ArrayList) readValue(buffer)); + case (byte) 132: return TestMessage.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -3551,11 +4187,14 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof AllNullableTypes) { stream.write(129); writeValue(stream, ((AllNullableTypes) value).toList()); - } else if (value instanceof AllTypes) { + } else if (value instanceof AllNullableTypesWithoutRecursion) { stream.write(130); + writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList()); + } else if (value instanceof AllTypes) { + stream.write(131); writeValue(stream, ((AllTypes) value).toList()); } else if (value instanceof TestMessage) { - stream.write(131); + stream.write(132); writeValue(stream, ((TestMessage) value).toList()); } else { super.writeValue(stream, value); @@ -3764,6 +4403,79 @@ public void sendMultipleNullableTypes( } }); } + /** Returns the passed object, to test serialization and deserialization. */ + public void echoAllNullableTypesWithoutRecursion( + @Nullable AllNullableTypesWithoutRecursion everythingArg, + @NonNull NullableResult result) { + final String channelName = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion"; + BasicMessageChannel channel = + new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); + channel.send( + new ArrayList(Collections.singletonList(everythingArg)), + channelReply -> { + if (channelReply instanceof List) { + List listReply = (List) channelReply; + if (listReply.size() > 1) { + result.error( + new FlutterError( + (String) listReply.get(0), + (String) listReply.get(1), + (String) listReply.get(2))); + } else { + @SuppressWarnings("ConstantConditions") + AllNullableTypesWithoutRecursion output = + (AllNullableTypesWithoutRecursion) listReply.get(0); + result.success(output); + } + } else { + result.error(createConnectionError(channelName)); + } + }); + } + /** + * Returns passed in arguments of multiple types. + * + *

Tests multiple-arity FlutterApi handling. + */ + public void sendMultipleNullableTypesWithoutRecursion( + @Nullable Boolean aNullableBoolArg, + @Nullable Long aNullableIntArg, + @Nullable String aNullableStringArg, + @NonNull Result result) { + final String channelName = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion"; + BasicMessageChannel channel = + new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); + channel.send( + new ArrayList( + Arrays.asList(aNullableBoolArg, aNullableIntArg, aNullableStringArg)), + channelReply -> { + if (channelReply instanceof List) { + List listReply = (List) channelReply; + if (listReply.size() > 1) { + result.error( + new FlutterError( + (String) listReply.get(0), + (String) listReply.get(1), + (String) listReply.get(2))); + } else if (listReply.get(0) == null) { + result.error( + new FlutterError( + "null-error", + "Flutter api returned null value for non-null return value.", + "")); + } else { + @SuppressWarnings("ConstantConditions") + AllNullableTypesWithoutRecursion output = + (AllNullableTypesWithoutRecursion) listReply.get(0); + result.success(output); + } + } else { + result.error(createConnectionError(channelName)); + } + }); + } /** Returns the passed boolean, to test serialization and deserialization. */ public void echoBool(@NonNull Boolean aBoolArg, @NonNull Result result) { final String channelName = diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AllDatatypesTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AllDatatypesTest.m index 4ac3d8a6129a..bcc8c4972a7d 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AllDatatypesTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AllDatatypesTest.m @@ -17,14 +17,14 @@ @interface AllDatatypesTest : XCTestCase @implementation AllDatatypesTest - (void)testAllNull { - AllNullableTypes *everything = [[AllNullableTypes alloc] init]; + FLTAllNullableTypes *everything = [[FLTAllNullableTypes alloc] init]; EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:FlutterIntegrationCoreApiGetCodec()]; - FlutterIntegrationCoreApi *api = - [[FlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; + [[EchoBinaryMessenger alloc] initWithCodec:FLTFlutterIntegrationCoreApiGetCodec()]; + FLTFlutterIntegrationCoreApi *api = + [[FLTFlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api echoAllNullableTypes:everything - completion:^(AllNullableTypes *_Nonnull result, FlutterError *_Nullable error) { + completion:^(FLTAllNullableTypes *_Nonnull result, FlutterError *_Nullable error) { XCTAssertNil(result.aNullableBool); XCTAssertNil(result.aNullableInt); XCTAssertNil(result.aNullableDouble); @@ -41,7 +41,7 @@ - (void)testAllNull { } - (void)testAllEquals { - AllNullableTypes *everything = [[AllNullableTypes alloc] init]; + FLTAllNullableTypes *everything = [[FLTAllNullableTypes alloc] init]; everything.aNullableBool = @NO; everything.aNullableInt = @(1); everything.aNullableDouble = @(2.0); @@ -58,12 +58,12 @@ - (void)testAllEquals { everything.aNullableMap = @{@"hello" : @(1234)}; everything.nullableMapWithObject = @{@"hello" : @(1234), @"goodbye" : @"world"}; EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:FlutterIntegrationCoreApiGetCodec()]; - FlutterIntegrationCoreApi *api = - [[FlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; + [[EchoBinaryMessenger alloc] initWithCodec:FLTFlutterIntegrationCoreApiGetCodec()]; + FLTFlutterIntegrationCoreApi *api = + [[FLTFlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api echoAllNullableTypes:everything - completion:^(AllNullableTypes *_Nonnull result, FlutterError *_Nullable error) { + completion:^(FLTAllNullableTypes *_Nonnull result, FlutterError *_Nullable error) { XCTAssertEqual(result.aNullableBool, everything.aNullableBool); XCTAssertEqual(result.aNullableInt, everything.aNullableInt); XCTAssertEqual(result.aNullableDouble, everything.aNullableDouble); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AsyncHandlersTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AsyncHandlersTest.m index 7034618a2396..660911086c38 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AsyncHandlersTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/AsyncHandlersTest.m @@ -10,7 +10,7 @@ #import "MockBinaryMessenger.h" /////////////////////////////////////////////////////////////////////////////////////////// -@interface MockHostSmallApi : NSObject +@interface MockHostSmallApi : NSObject @property(nonatomic, copy) NSString *output; @property(nonatomic, retain) FlutterError *voidVoidError; @end @@ -42,11 +42,11 @@ @implementation AsyncHandlersTest - (void)testAsyncHost2Flutter { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:FlutterIntegrationCoreApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTFlutterIntegrationCoreApiGetCodec()]; NSString *value = @"Test"; binaryMessenger.result = value; - FlutterIntegrationCoreApi *flutterApi = - [[FlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; + FLTFlutterIntegrationCoreApi *flutterApi = + [[FLTFlutterIntegrationCoreApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"echo callback"]; [flutterApi echoAsyncString:value completion:^(NSString *_Nonnull output, FlutterError *_Nullable error) { @@ -58,9 +58,9 @@ - (void)testAsyncHost2Flutter { - (void)testAsyncFlutter2HostVoidVoid { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:HostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; - SetUpHostSmallApi(binaryMessenger, mockHostSmallApi); + SetUpFLTHostSmallApi(binaryMessenger, mockHostSmallApi); NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.voidVoid"; XCTAssertNotNil(binaryMessenger.handlers[channelName]); @@ -75,12 +75,12 @@ - (void)testAsyncFlutter2HostVoidVoid { - (void)testAsyncFlutter2HostVoidVoidError { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:HostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; mockHostSmallApi.voidVoidError = [FlutterError errorWithCode:@"code" message:@"message" details:nil]; - SetUpHostSmallApi(binaryMessenger, mockHostSmallApi); + SetUpFLTHostSmallApi(binaryMessenger, mockHostSmallApi); NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.voidVoid"; XCTAssertNotNil(binaryMessenger.handlers[channelName]); @@ -96,11 +96,11 @@ - (void)testAsyncFlutter2HostVoidVoidError { - (void)testAsyncFlutter2Host { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:HostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; NSString *value = @"Test"; mockHostSmallApi.output = value; - SetUpHostSmallApi(binaryMessenger, mockHostSmallApi); + SetUpFLTHostSmallApi(binaryMessenger, mockHostSmallApi); NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo"; XCTAssertNotNil(binaryMessenger.handlers[channelName]); @@ -117,9 +117,9 @@ - (void)testAsyncFlutter2Host { - (void)testAsyncFlutter2HostError { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:HostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; - SetUpHostSmallApi(binaryMessenger, mockHostSmallApi); + SetUpFLTHostSmallApi(binaryMessenger, mockHostSmallApi); NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo"; XCTAssertNotNil(binaryMessenger.handlers[channelName]); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m index 124de85973b2..54bea7f0e781 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m @@ -17,18 +17,18 @@ @interface ListTest : XCTestCase @implementation ListTest - (void)testListInList { - TestMessage *top = [[TestMessage alloc] init]; - TestMessage *inside = [[TestMessage alloc] init]; + FLTTestMessage *top = [[FLTTestMessage alloc] init]; + FLTTestMessage *inside = [[FLTTestMessage alloc] init]; inside.testList = @[ @1, @2, @3 ]; top.testList = @[ inside ]; EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:FlutterSmallApiGetCodec()]; - FlutterSmallApi *api = [[FlutterSmallApi alloc] initWithBinaryMessenger:binaryMessenger]; + [[EchoBinaryMessenger alloc] initWithCodec:FLTFlutterSmallApiGetCodec()]; + FLTFlutterSmallApi *api = [[FLTFlutterSmallApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api echoWrappedList:top - completion:^(TestMessage *_Nonnull result, FlutterError *_Nullable err) { + completion:^(FLTTestMessage *_Nonnull result, FlutterError *_Nullable err) { XCTAssertEqual(1u, result.testList.count); - XCTAssertTrue([result.testList[0] isKindOfClass:[TestMessage class]]); + XCTAssertTrue([result.testList[0] isKindOfClass:[FLTTestMessage class]]); XCTAssertEqualObjects(inside.testList, [result.testList[0] testList]); [expectation fulfill]; }]; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.h index 96ac05a7de1e..afb911d8e00d 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.h @@ -6,5 +6,5 @@ #import "CoreTests.gen.h" -@interface AlternateLanguageTestPlugin : NSObject +@interface AlternateLanguageTestPlugin : NSObject @end diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m index d1e42a10ccaa..c3501d5ce7ae 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m @@ -7,18 +7,16 @@ #import "CoreTests.gen.h" @interface AlternateLanguageTestPlugin () -@property(nonatomic) FlutterIntegrationCoreApi *flutterAPI; +@property(nonatomic) FLTFlutterIntegrationCoreApi *flutterAPI; @end -/** - * This plugin handles the native side of the integration tests in example/integration_test/. - */ +/// This plugin handles the native side of the integration tests in example/integration_test/. @implementation AlternateLanguageTestPlugin + (void)registerWithRegistrar:(NSObject *)registrar { AlternateLanguageTestPlugin *plugin = [[AlternateLanguageTestPlugin alloc] init]; - SetUpHostIntegrationCoreApi([registrar messenger], plugin); + SetUpFLTHostIntegrationCoreApi([registrar messenger], plugin); plugin.flutterAPI = - [[FlutterIntegrationCoreApi alloc] initWithBinaryMessenger:[registrar messenger]]; + [[FLTFlutterIntegrationCoreApi alloc] initWithBinaryMessenger:[registrar messenger]]; } #pragma mark HostIntegrationCoreApi implementation @@ -26,13 +24,19 @@ + (void)registerWithRegistrar:(NSObject *)registrar { - (void)noopWithError:(FlutterError *_Nullable *_Nonnull)error { } -- (nullable AllTypes *)echoAllTypes:(AllTypes *)everything - error:(FlutterError *_Nullable *_Nonnull)error { +- (nullable FLTAllTypes *)echoAllTypes:(FLTAllTypes *)everything + error:(FlutterError *_Nullable *_Nonnull)error { return everything; } -- (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes *)everything - error:(FlutterError *_Nullable *_Nonnull)error { +- (nullable FLTAllNullableTypes *)echoAllNullableTypes:(nullable FLTAllNullableTypes *)everything + error:(FlutterError *_Nullable *_Nonnull)error { + return everything; +} + +- (nullable FLTAllNullableTypesWithoutRecursion *) + echoAllNullableTypesWithoutRecursion:(nullable FLTAllNullableTypesWithoutRecursion *)everything + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return everything; } @@ -86,13 +90,14 @@ - (nullable id)echoObject:(id)anObject error:(FlutterError *_Nullable *_Nonnull) return aMap; } -- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper - error:(FlutterError *_Nullable *_Nonnull)error { +- (nullable FLTAllClassesWrapper *)echoClassWrapper:(FLTAllClassesWrapper *)wrapper + error:(FlutterError *_Nullable *_Nonnull)error { return wrapper; } -- (AnEnumBox *_Nullable)echoEnum:(AnEnum)anEnum error:(FlutterError *_Nullable *_Nonnull)error { - return [[AnEnumBox alloc] initWithValue:anEnum]; +- (FLTAnEnumBox *_Nullable)echoEnum:(FLTAnEnum)anEnum + error:(FlutterError *_Nullable *_Nonnull)error { + return [[FLTAnEnumBox alloc] initWithValue:anEnum]; } - (nullable NSString *)echoNamedDefaultString:(NSString *)aString @@ -110,25 +115,42 @@ - (nullable NSNumber *)echoRequiredInt:(NSInteger)anInt return @(anInt); } -- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper +- (nullable NSString *)extractNestedNullableStringFrom:(FLTAllClassesWrapper *)wrapper error:(FlutterError *_Nullable *_Nonnull)error { return wrapper.allNullableTypes.aNullableString; } -- (nullable AllClassesWrapper *) +- (nullable FLTAllClassesWrapper *) createNestedObjectWithNullableString:(nullable NSString *)nullableString error:(FlutterError *_Nullable *_Nonnull)error { - AllNullableTypes *innerObject = [[AllNullableTypes alloc] init]; + FLTAllNullableTypes *innerObject = [[FLTAllNullableTypes alloc] init]; innerObject.aNullableString = nullableString; - return [AllClassesWrapper makeWithAllNullableTypes:innerObject allTypes:nil]; + return [FLTAllClassesWrapper makeWithAllNullableTypes:innerObject + allNullableTypesWithoutRecursion:nil + allTypes:nil]; +} + +- (nullable FLTAllNullableTypes *) + sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error:(FlutterError *_Nullable *_Nonnull)error { + FLTAllNullableTypes *someTypes = [[FLTAllNullableTypes alloc] init]; + someTypes.aNullableBool = aNullableBool; + someTypes.aNullableInt = aNullableInt; + someTypes.aNullableString = aNullableString; + return someTypes; } -- (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool - anInt:(nullable NSNumber *)aNullableInt - aString:(nullable NSString *)aNullableString - error:(FlutterError *_Nullable *_Nonnull) - error { - AllNullableTypes *someTypes = [[AllNullableTypes alloc] init]; +- (nullable FLTAllNullableTypesWithoutRecursion *) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error: + (FlutterError *_Nullable __autoreleasing *_Nonnull) + error { + FLTAllNullableTypesWithoutRecursion *someTypes = + [[FLTAllNullableTypesWithoutRecursion alloc] init]; someTypes.aNullableBool = aNullableBool; someTypes.aNullableInt = aNullableInt; someTypes.aNullableString = aNullableString; @@ -177,8 +199,8 @@ - (nullable id)echoNullableObject:(nullable id)aNullableObject return aNullableMap; } -- (AnEnumBox *_Nullable)echoNullableEnum:(nullable AnEnumBox *)AnEnumBoxed - error:(FlutterError *_Nullable *_Nonnull)error { +- (FLTAnEnumBox *_Nullable)echoNullableEnum:(nullable FLTAnEnumBox *)AnEnumBoxed + error:(FlutterError *_Nullable *_Nonnull)error { return AnEnumBoxed; } @@ -209,17 +231,27 @@ - (void)throwAsyncFlutterErrorWithCompletion:(void (^)(id _Nullable, completion(nil, [FlutterError errorWithCode:@"code" message:@"message" details:@"details"]); } -- (void)echoAsyncAllTypes:(AllTypes *)everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion { +- (void)echoAsyncAllTypes:(FLTAllTypes *)everything + completion:(void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion { completion(everything, nil); } -- (void)echoAsyncNullableAllNullableTypes:(nullable AllNullableTypes *)everything - completion:(void (^)(AllNullableTypes *_Nullable, +- (void)echoAsyncNullableAllNullableTypes:(nullable FLTAllNullableTypes *)everything + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion { completion(everything, nil); } +- (void) + echoAsyncNullableAllNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)everything + completion: + (nonnull void (^)( + FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + completion(everything, nil); +} + - (void)echoAsyncInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { completion(@(anInt), nil); @@ -262,9 +294,9 @@ - (void)echoAsyncMap:(NSDictionary *)aMap completion(aMap, nil); } -- (void)echoAsyncEnum:(AnEnum)anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { - completion([[AnEnumBox alloc] initWithValue:anEnum], nil); +- (void)echoAsyncEnum:(FLTAnEnum)anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion { + completion([[FLTAnEnumBox alloc] initWithValue:anEnum], nil); } - (void)echoAsyncNullableInt:(nullable NSNumber *)anInt @@ -310,8 +342,9 @@ - (void)echoAsyncNullableMap:(nullable NSDictionary *)aMap completion(aMap, nil); } -- (void)echoAsyncNullableEnum:(nullable AnEnumBox *)AnEnumBoxed - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { +- (void)echoAsyncNullableEnum:(nullable FLTAnEnumBox *)AnEnumBoxed + completion: + (void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion { completion(AnEnumBoxed, nil); } @@ -334,10 +367,11 @@ - (void)callFlutterThrowErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nul }]; } -- (void)callFlutterEchoAllTypes:(AllTypes *)everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion { +- (void)callFlutterEchoAllTypes:(FLTAllTypes *)everything + completion: + (void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoAllTypes:everything - completion:^(AllTypes *value, FlutterError *error) { + completion:^(FLTAllTypes *value, FlutterError *error) { completion(value, error); }]; } @@ -345,14 +379,34 @@ - (void)callFlutterEchoAllTypes:(AllTypes *)everything - (void)callFlutterSendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool anInt:(nullable NSNumber *)aNullableInt aString:(nullable NSString *)aNullableString - completion:(void (^)(AllNullableTypes *_Nullable, + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI sendMultipleNullableTypesABool:aNullableBool - anInt:aNullableInt - aString:aNullableString - completion:^(AllNullableTypes *value, FlutterError *error) { - completion(value, error); - }]; + [self.flutterAPI + sendMultipleNullableTypesABool:aNullableBool + anInt:aNullableInt + aString:aNullableString + completion:^(FLTAllNullableTypes *value, FlutterError *error) { + completion(value, error); + }]; +} + +- (void)callFlutterSendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString: + (nullable NSString *)aNullableString + completion: + (nonnull void (^)( + FLTAllNullableTypesWithoutRecursion + *_Nullable, + FlutterError *_Nullable))completion { + [self.flutterAPI + sendMultipleNullableTypesWithoutRecursionABool:aNullableBool + anInt:aNullableInt + aString:aNullableString + completion:^(FLTAllNullableTypesWithoutRecursion *value, + FlutterError *error) { + completion(value, error); + }]; } - (void)callFlutterEchoBool:(BOOL)aBool @@ -413,23 +467,37 @@ - (void)callFlutterEchoMap:(NSDictionary *)aMap }]; } -- (void)callFlutterEchoEnum:(AnEnum)anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { +- (void)callFlutterEchoEnum:(FLTAnEnum)anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoEnum:anEnum - completion:^(AnEnumBox *value, FlutterError *error) { + completion:^(FLTAnEnumBox *value, FlutterError *error) { completion(value, error); }]; } -- (void)callFlutterEchoAllNullableTypes:(nullable AllNullableTypes *)everything - completion:(void (^)(AllNullableTypes *_Nullable, +- (void)callFlutterEchoAllNullableTypes:(nullable FLTAllNullableTypes *)everything + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoAllNullableTypes:everything - completion:^(AllNullableTypes *value, FlutterError *error) { + completion:^(FLTAllNullableTypes *value, FlutterError *error) { completion(value, error); }]; } +- (void)callFlutterEchoAllNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)everything + completion: + (nonnull void (^)( + FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + [self.flutterAPI + echoAllNullableTypesWithoutRecursion:everything + completion:^(FLTAllNullableTypesWithoutRecursion *value, + FlutterError *error) { + completion(value, error); + }]; +} + - (void)callFlutterEchoNullableBool:(nullable NSNumber *)aBool completion: (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { @@ -493,11 +561,11 @@ - (void)callFlutterEchoNullableMap:(nullable NSDictionary *)aMap }]; } -- (void)callFlutterEchoNullableEnum:(nullable AnEnumBox *)AnEnumBoxed - completion: - (void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { +- (void)callFlutterEchoNullableEnum:(nullable FLTAnEnumBox *)AnEnumBoxed + completion:(void (^)(FLTAnEnumBox *_Nullable, + FlutterError *_Nullable))completion { [self.flutterAPI echoNullableEnum:AnEnumBoxed - completion:^(AnEnumBox *value, FlutterError *error) { + completion:^(FLTAnEnumBox *value, FlutterError *error) { completion(value, error); }]; } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h index e7022baa9403..72c1e9d2ec22 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h @@ -14,27 +14,28 @@ NS_ASSUME_NONNULL_BEGIN -typedef NS_ENUM(NSUInteger, AnEnum) { - AnEnumOne = 0, - AnEnumTwo = 1, - AnEnumThree = 2, - AnEnumFortyTwo = 3, - AnEnumFourHundredTwentyTwo = 4, +typedef NS_ENUM(NSUInteger, FLTAnEnum) { + FLTAnEnumOne = 0, + FLTAnEnumTwo = 1, + FLTAnEnumThree = 2, + FLTAnEnumFortyTwo = 3, + FLTAnEnumFourHundredTwentyTwo = 4, }; -/// Wrapper for AnEnum to allow for nullability. -@interface AnEnumBox : NSObject -@property(nonatomic, assign) AnEnum value; -- (instancetype)initWithValue:(AnEnum)value; +/// Wrapper for FLTAnEnum to allow for nullability. +@interface FLTAnEnumBox : NSObject +@property(nonatomic, assign) FLTAnEnum value; +- (instancetype)initWithValue:(FLTAnEnum)value; @end -@class AllTypes; -@class AllNullableTypes; -@class AllClassesWrapper; -@class TestMessage; +@class FLTAllTypes; +@class FLTAllNullableTypes; +@class FLTAllNullableTypesWithoutRecursion; +@class FLTAllClassesWrapper; +@class FLTTestMessage; /// A class containing all supported types. -@interface AllTypes : NSObject +@interface FLTAllTypes : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithABool:(BOOL)aBool @@ -47,7 +48,7 @@ typedef NS_ENUM(NSUInteger, AnEnum) { aFloatArray:(FlutterStandardTypedData *)aFloatArray aList:(NSArray *)aList aMap:(NSDictionary *)aMap - anEnum:(AnEnum)anEnum + anEnum:(FLTAnEnum)anEnum aString:(NSString *)aString anObject:(id)anObject; @property(nonatomic, assign) BOOL aBool; @@ -60,13 +61,13 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, strong) FlutterStandardTypedData *aFloatArray; @property(nonatomic, copy) NSArray *aList; @property(nonatomic, copy) NSDictionary *aMap; -@property(nonatomic, assign) AnEnum anEnum; +@property(nonatomic, assign) FLTAnEnum anEnum; @property(nonatomic, copy) NSString *aString; @property(nonatomic, strong) id anObject; @end /// A class containing all supported nullable types. -@interface AllNullableTypes : NSObject +@interface FLTAllNullableTypes : NSObject + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullableInt:(nullable NSNumber *)aNullableInt aNullableInt64:(nullable NSNumber *)aNullableInt64 @@ -81,7 +82,49 @@ typedef NS_ENUM(NSUInteger, AnEnum) { nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject - aNullableEnum:(nullable AnEnumBox *)aNullableEnum + aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum + aNullableString:(nullable NSString *)aNullableString + aNullableObject:(nullable id)aNullableObject + allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes; +@property(nonatomic, strong, nullable) NSNumber *aNullableBool; +@property(nonatomic, strong, nullable) NSNumber *aNullableInt; +@property(nonatomic, strong, nullable) NSNumber *aNullableInt64; +@property(nonatomic, strong, nullable) NSNumber *aNullableDouble; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable4ByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable8ByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableFloatArray; +@property(nonatomic, copy, nullable) NSArray *aNullableList; +@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; +@property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; +@property(nonatomic, copy, nullable) + NSDictionary *nullableMapWithAnnotations; +@property(nonatomic, copy, nullable) NSDictionary *nullableMapWithObject; +@property(nonatomic, strong, nullable) FLTAnEnumBox *aNullableEnum; +@property(nonatomic, copy, nullable) NSString *aNullableString; +@property(nonatomic, strong, nullable) id aNullableObject; +@property(nonatomic, strong, nullable) FLTAllNullableTypes *allNullableTypes; +@end + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +@interface FLTAllNullableTypesWithoutRecursion : NSObject ++ (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool + aNullableInt:(nullable NSNumber *)aNullableInt + aNullableInt64:(nullable NSNumber *)aNullableInt64 + aNullableDouble:(nullable NSNumber *)aNullableDouble + aNullableByteArray:(nullable FlutterStandardTypedData *)aNullableByteArray + aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray + aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray + aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray + aNullableList:(nullable NSArray *)aNullableList + aNullableMap:(nullable NSDictionary *)aNullableMap + nullableNestedList:(nullable NSArray *> *)nullableNestedList + nullableMapWithAnnotations: + (nullable NSDictionary *)nullableMapWithAnnotations + nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject + aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject; @property(nonatomic, strong, nullable) NSNumber *aNullableBool; @@ -98,7 +141,7 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithAnnotations; @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithObject; -@property(nonatomic, strong, nullable) AnEnumBox *aNullableEnum; +@property(nonatomic, strong, nullable) FLTAnEnumBox *aNullableEnum; @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; @end @@ -108,35 +151,39 @@ typedef NS_ENUM(NSUInteger, AnEnum) { /// This is needed to test nested nullable and non-nullable classes, /// `AllNullableTypes` is non-nullable here as it is easier to instantiate /// than `AllTypes` when testing doesn't require both (ie. testing null classes). -@interface AllClassesWrapper : NSObject +@interface FLTAllClassesWrapper : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes - allTypes:(nullable AllTypes *)allTypes; -@property(nonatomic, strong) AllNullableTypes *allNullableTypes; -@property(nonatomic, strong, nullable) AllTypes *allTypes; ++ (instancetype)makeWithAllNullableTypes:(FLTAllNullableTypes *)allNullableTypes + allNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)allNullableTypesWithoutRecursion + allTypes:(nullable FLTAllTypes *)allTypes; +@property(nonatomic, strong) FLTAllNullableTypes *allNullableTypes; +@property(nonatomic, strong, nullable) + FLTAllNullableTypesWithoutRecursion *allNullableTypesWithoutRecursion; +@property(nonatomic, strong, nullable) FLTAllTypes *allTypes; @end /// A data class containing a List, used in unit tests. -@interface TestMessage : NSObject +@interface FLTTestMessage : NSObject + (instancetype)makeWithTestList:(nullable NSArray *)testList; @property(nonatomic, copy, nullable) NSArray *testList; @end -/// The codec used by HostIntegrationCoreApi. -NSObject *HostIntegrationCoreApiGetCodec(void); +/// The codec used by FLTHostIntegrationCoreApi. +NSObject *FLTHostIntegrationCoreApiGetCodec(void); /// The core interface that each host language plugin must implement in /// platform_test integration tests. -@protocol HostIntegrationCoreApi +@protocol FLTHostIntegrationCoreApi /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. - (void)noopWithError:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed object, to test serialization and deserialization. /// /// @return `nil` only when `error != nil`. -- (nullable AllTypes *)echoAllTypes:(AllTypes *)everything - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FLTAllTypes *)echoAllTypes:(FLTAllTypes *)everything + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns an error, to test error handling. - (nullable id)throwErrorWithError:(FlutterError *_Nullable *_Nonnull)error; /// Returns an error from a void function, to test error handling. @@ -182,12 +229,13 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// Returns the passed map to test nested class serialization and deserialization. /// /// @return `nil` only when `error != nil`. -- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FLTAllClassesWrapper *)echoClassWrapper:(FLTAllClassesWrapper *)wrapper + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed enum to test serialization and deserialization. /// /// @return `nil` only when `error != nil`. -- (AnEnumBox *_Nullable)echoEnum:(AnEnum)anEnum error:(FlutterError *_Nullable *_Nonnull)error; +- (FLTAnEnumBox *_Nullable)echoEnum:(FLTAnEnum)anEnum + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the default string. /// /// @return `nil` only when `error != nil`. @@ -204,27 +252,39 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (nullable NSNumber *)echoRequiredInt:(NSInteger)anInt error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed object, to test serialization and deserialization. -- (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes *)everything - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FLTAllNullableTypes *)echoAllNullableTypes:(nullable FLTAllNullableTypes *)everything + error:(FlutterError *_Nullable *_Nonnull)error; +/// Returns the passed object, to test serialization and deserialization. +- (nullable FLTAllNullableTypesWithoutRecursion *) + echoAllNullableTypesWithoutRecursion:(nullable FLTAllNullableTypesWithoutRecursion *)everything + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. -- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper +- (nullable NSString *)extractNestedNullableStringFrom:(FLTAllClassesWrapper *)wrapper error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. /// /// @return `nil` only when `error != nil`. -- (nullable AllClassesWrapper *) +- (nullable FLTAllClassesWrapper *) createNestedObjectWithNullableString:(nullable NSString *)nullableString error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in arguments of multiple types. /// /// @return `nil` only when `error != nil`. -- (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool - anInt:(nullable NSNumber *)aNullableInt - aString:(nullable NSString *)aNullableString - error:(FlutterError *_Nullable *_Nonnull) - error; +- (nullable FLTAllNullableTypes *) + sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error:(FlutterError *_Nullable *_Nonnull)error; +/// Returns passed in arguments of multiple types. +/// +/// @return `nil` only when `error != nil`. +- (nullable FLTAllNullableTypesWithoutRecursion *) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in int. - (nullable NSNumber *)echoNullableInt:(nullable NSNumber *)aNullableInt error:(FlutterError *_Nullable *_Nonnull)error; @@ -251,8 +311,8 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (nullable NSDictionary *)echoNullableMap: (nullable NSDictionary *)aNullableMap error:(FlutterError *_Nullable *_Nonnull)error; -- (AnEnumBox *_Nullable)echoNullableEnum:(nullable AnEnumBox *)anEnumBoxed - error:(FlutterError *_Nullable *_Nonnull)error; +- (FLTAnEnumBox *_Nullable)echoNullableEnum:(nullable FLTAnEnumBox *)anEnumBoxed + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in int. - (nullable NSNumber *)echoOptionalNullableInt:(nullable NSNumber *)aNullableInt error:(FlutterError *_Nullable *_Nonnull)error; @@ -289,8 +349,8 @@ NSObject *HostIntegrationCoreApiGetCodec(void); completion:(void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed enum, to test asynchronous serialization and deserialization. -- (void)echoAsyncEnum:(AnEnum)anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; +- (void)echoAsyncEnum:(FLTAnEnum)anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; /// Responds with an error from an async function returning a value. - (void)throwAsyncErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Responds with an error from an async void function. @@ -299,12 +359,19 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)throwAsyncFlutterErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Returns the passed object, to test async serialization and deserialization. -- (void)echoAsyncAllTypes:(AllTypes *)everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion; +- (void)echoAsyncAllTypes:(FLTAllTypes *)everything + completion:(void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed object, to test serialization and deserialization. -- (void)echoAsyncNullableAllNullableTypes:(nullable AllNullableTypes *)everything - completion:(void (^)(AllNullableTypes *_Nullable, +- (void)echoAsyncNullableAllNullableTypes:(nullable FLTAllNullableTypes *)everything + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +/// Returns the passed object, to test serialization and deserialization. +- (void)echoAsyncNullableAllNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)everything + completion: + (void (^)(FLTAllNullableTypesWithoutRecursion + *_Nullable, + FlutterError *_Nullable))completion; /// Returns passed in int asynchronously. - (void)echoAsyncNullableInt:(nullable NSNumber *)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @@ -332,22 +399,39 @@ NSObject *HostIntegrationCoreApiGetCodec(void); completion:(void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed enum, to test asynchronous serialization and deserialization. -- (void)echoAsyncNullableEnum:(nullable AnEnumBox *)anEnumBoxed - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; +- (void)echoAsyncNullableEnum:(nullable FLTAnEnumBox *)anEnumBoxed + completion: + (void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterNoopWithCompletion:(void (^)(FlutterError *_Nullable))completion; - (void)callFlutterThrowErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; - (void)callFlutterThrowErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))completion; -- (void)callFlutterEchoAllTypes:(AllTypes *)everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoAllNullableTypes:(nullable AllNullableTypes *)everything - completion:(void (^)(AllNullableTypes *_Nullable, +- (void)callFlutterEchoAllTypes:(FLTAllTypes *)everything + completion: + (void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion; +- (void)callFlutterEchoAllNullableTypes:(nullable FLTAllNullableTypes *)everything + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterSendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool anInt:(nullable NSNumber *)aNullableInt aString:(nullable NSString *)aNullableString - completion:(void (^)(AllNullableTypes *_Nullable, + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +- (void)callFlutterEchoAllNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)everything + completion: + (void (^)( + FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; +- (void) + callFlutterSendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + completion: + (void (^)(FLTAllNullableTypesWithoutRecursion + *_Nullable, + FlutterError *_Nullable)) + completion; - (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoInt:(NSInteger)anInt @@ -364,8 +448,8 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)callFlutterEchoMap:(NSDictionary *)aMap completion:(void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoEnum:(AnEnum)anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; +- (void)callFlutterEchoEnum:(FLTAnEnum)anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoNullableBool:(nullable NSNumber *)aBool completion: (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @@ -387,20 +471,20 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)callFlutterEchoNullableMap:(nullable NSDictionary *)aMap completion:(void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoNullableEnum:(nullable AnEnumBox *)anEnumBoxed +- (void)callFlutterEchoNullableEnum:(nullable FLTAnEnumBox *)anEnumBoxed completion: - (void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; + (void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; @end -extern void SetUpHostIntegrationCoreApi(id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFLTHostIntegrationCoreApi(id binaryMessenger, + NSObject *_Nullable api); -/// The codec used by FlutterIntegrationCoreApi. -NSObject *FlutterIntegrationCoreApiGetCodec(void); +/// The codec used by FLTFlutterIntegrationCoreApi. +NSObject *FLTFlutterIntegrationCoreApiGetCodec(void); /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. -@interface FlutterIntegrationCoreApi : NSObject +@interface FLTFlutterIntegrationCoreApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. @@ -410,20 +494,35 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); /// Responds with an error from an async void function. - (void)throwErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))completion; /// Returns the passed object, to test serialization and deserialization. -- (void)echoAllTypes:(AllTypes *)everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion; +- (void)echoAllTypes:(FLTAllTypes *)everything + completion:(void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed object, to test serialization and deserialization. -- (void)echoAllNullableTypes:(nullable AllNullableTypes *)everything +- (void)echoAllNullableTypes:(nullable FLTAllNullableTypes *)everything completion: - (void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; + (void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion; /// Returns passed in arguments of multiple types. /// /// Tests multiple-arity FlutterApi handling. - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool anInt:(nullable NSNumber *)aNullableInt aString:(nullable NSString *)aNullableString - completion:(void (^)(AllNullableTypes *_Nullable, + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +/// Returns the passed object, to test serialization and deserialization. +- (void) + echoAllNullableTypesWithoutRecursion:(nullable FLTAllNullableTypesWithoutRecursion *)everything + completion:(void (^)(FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; +/// Returns passed in arguments of multiple types. +/// +/// Tests multiple-arity FlutterApi handling. +- (void)sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + completion: + (void (^)( + FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; /// Returns the passed boolean, to test serialization and deserialization. - (void)echoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @@ -448,8 +547,8 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion: (void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed enum to test serialization and deserialization. -- (void)echoEnum:(AnEnum)anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; +- (void)echoEnum:(FLTAnEnum)anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed boolean, to test serialization and deserialization. - (void)echoNullableBool:(nullable NSNumber *)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @@ -474,8 +573,8 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(NSDictionary *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed enum to test serialization and deserialization. -- (void)echoNullableEnum:(nullable AnEnumBox *)anEnumBoxed - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion; +- (void)echoNullableEnum:(nullable FLTAnEnumBox *)anEnumBoxed + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion; /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion; @@ -484,38 +583,38 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @end -/// The codec used by HostTrivialApi. -NSObject *HostTrivialApiGetCodec(void); +/// The codec used by FLTHostTrivialApi. +NSObject *FLTHostTrivialApiGetCodec(void); /// An API that can be implemented for minimal, compile-only tests. -@protocol HostTrivialApi +@protocol FLTHostTrivialApi - (void)noopWithError:(FlutterError *_Nullable *_Nonnull)error; @end -extern void SetUpHostTrivialApi(id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFLTHostTrivialApi(id binaryMessenger, + NSObject *_Nullable api); -/// The codec used by HostSmallApi. -NSObject *HostSmallApiGetCodec(void); +/// The codec used by FLTHostSmallApi. +NSObject *FLTHostSmallApiGetCodec(void); /// A simple API implemented in some unit tests. -@protocol HostSmallApi +@protocol FLTHostSmallApi - (void)echoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; - (void)voidVoidWithCompletion:(void (^)(FlutterError *_Nullable))completion; @end -extern void SetUpHostSmallApi(id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFLTHostSmallApi(id binaryMessenger, + NSObject *_Nullable api); -/// The codec used by FlutterSmallApi. -NSObject *FlutterSmallApiGetCodec(void); +/// The codec used by FLTFlutterSmallApi. +NSObject *FLTFlutterSmallApiGetCodec(void); /// A simple API called in some unit tests. -@interface FlutterSmallApi : NSObject +@interface FLTFlutterSmallApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)echoWrappedList:(TestMessage *)msg - completion:(void (^)(TestMessage *_Nullable, FlutterError *_Nullable))completion; +- (void)echoWrappedList:(FLTTestMessage *)msg + completion:(void (^)(FLTTestMessage *_Nullable, FlutterError *_Nullable))completion; - (void)echoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @end diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m index 0cc5b059c04f..2861a8880485 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m @@ -40,8 +40,8 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { return (result == [NSNull null]) ? nil : result; } -@implementation AnEnumBox -- (instancetype)initWithValue:(AnEnum)value { +@implementation FLTAnEnumBox +- (instancetype)initWithValue:(FLTAnEnum)value { self = [super init]; if (self) { _value = value; @@ -50,31 +50,37 @@ - (instancetype)initWithValue:(AnEnum)value { } @end -@interface AllTypes () -+ (AllTypes *)fromList:(NSArray *)list; -+ (nullable AllTypes *)nullableFromList:(NSArray *)list; +@interface FLTAllTypes () ++ (FLTAllTypes *)fromList:(NSArray *)list; ++ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end -@interface AllNullableTypes () -+ (AllNullableTypes *)fromList:(NSArray *)list; -+ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list; +@interface FLTAllNullableTypes () ++ (FLTAllNullableTypes *)fromList:(NSArray *)list; ++ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end -@interface AllClassesWrapper () -+ (AllClassesWrapper *)fromList:(NSArray *)list; -+ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list; +@interface FLTAllNullableTypesWithoutRecursion () ++ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; ++ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end -@interface TestMessage () -+ (TestMessage *)fromList:(NSArray *)list; -+ (nullable TestMessage *)nullableFromList:(NSArray *)list; +@interface FLTAllClassesWrapper () ++ (FLTAllClassesWrapper *)fromList:(NSArray *)list; ++ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end -@implementation AllTypes +@interface FLTTestMessage () ++ (FLTTestMessage *)fromList:(NSArray *)list; ++ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@implementation FLTAllTypes + (instancetype)makeWithABool:(BOOL)aBool anInt:(NSInteger)anInt anInt64:(NSInteger)anInt64 @@ -85,10 +91,10 @@ + (instancetype)makeWithABool:(BOOL)aBool aFloatArray:(FlutterStandardTypedData *)aFloatArray aList:(NSArray *)aList aMap:(NSDictionary *)aMap - anEnum:(AnEnum)anEnum + anEnum:(FLTAnEnum)anEnum aString:(NSString *)aString anObject:(id)anObject { - AllTypes *pigeonResult = [[AllTypes alloc] init]; + FLTAllTypes *pigeonResult = [[FLTAllTypes alloc] init]; pigeonResult.aBool = aBool; pigeonResult.anInt = anInt; pigeonResult.anInt64 = anInt64; @@ -104,8 +110,8 @@ + (instancetype)makeWithABool:(BOOL)aBool pigeonResult.anObject = anObject; return pigeonResult; } -+ (AllTypes *)fromList:(NSArray *)list { - AllTypes *pigeonResult = [[AllTypes alloc] init]; ++ (FLTAllTypes *)fromList:(NSArray *)list { + FLTAllTypes *pigeonResult = [[FLTAllTypes alloc] init]; pigeonResult.aBool = [GetNullableObjectAtIndex(list, 0) boolValue]; pigeonResult.anInt = [GetNullableObjectAtIndex(list, 1) integerValue]; pigeonResult.anInt64 = [GetNullableObjectAtIndex(list, 2) integerValue]; @@ -121,8 +127,8 @@ + (AllTypes *)fromList:(NSArray *)list { pigeonResult.anObject = GetNullableObjectAtIndex(list, 12); return pigeonResult; } -+ (nullable AllTypes *)nullableFromList:(NSArray *)list { - return (list) ? [AllTypes fromList:list] : nil; ++ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list { + return (list) ? [FLTAllTypes fromList:list] : nil; } - (NSArray *)toList { return @[ @@ -143,7 +149,100 @@ - (NSArray *)toList { } @end -@implementation AllNullableTypes +@implementation FLTAllNullableTypes ++ (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool + aNullableInt:(nullable NSNumber *)aNullableInt + aNullableInt64:(nullable NSNumber *)aNullableInt64 + aNullableDouble:(nullable NSNumber *)aNullableDouble + aNullableByteArray:(nullable FlutterStandardTypedData *)aNullableByteArray + aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray + aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray + aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray + aNullableList:(nullable NSArray *)aNullableList + aNullableMap:(nullable NSDictionary *)aNullableMap + nullableNestedList:(nullable NSArray *> *)nullableNestedList + nullableMapWithAnnotations: + (nullable NSDictionary *)nullableMapWithAnnotations + nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject + aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum + aNullableString:(nullable NSString *)aNullableString + aNullableObject:(nullable id)aNullableObject + allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes { + FLTAllNullableTypes *pigeonResult = [[FLTAllNullableTypes alloc] init]; + pigeonResult.aNullableBool = aNullableBool; + pigeonResult.aNullableInt = aNullableInt; + pigeonResult.aNullableInt64 = aNullableInt64; + pigeonResult.aNullableDouble = aNullableDouble; + pigeonResult.aNullableByteArray = aNullableByteArray; + pigeonResult.aNullable4ByteArray = aNullable4ByteArray; + pigeonResult.aNullable8ByteArray = aNullable8ByteArray; + pigeonResult.aNullableFloatArray = aNullableFloatArray; + pigeonResult.aNullableList = aNullableList; + pigeonResult.aNullableMap = aNullableMap; + pigeonResult.nullableNestedList = nullableNestedList; + pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; + pigeonResult.nullableMapWithObject = nullableMapWithObject; + pigeonResult.aNullableEnum = aNullableEnum; + pigeonResult.aNullableString = aNullableString; + pigeonResult.aNullableObject = aNullableObject; + pigeonResult.allNullableTypes = allNullableTypes; + return pigeonResult; +} ++ (FLTAllNullableTypes *)fromList:(NSArray *)list { + FLTAllNullableTypes *pigeonResult = [[FLTAllNullableTypes alloc] init]; + pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); + pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); + pigeonResult.aNullableInt64 = GetNullableObjectAtIndex(list, 2); + pigeonResult.aNullableDouble = GetNullableObjectAtIndex(list, 3); + pigeonResult.aNullableByteArray = GetNullableObjectAtIndex(list, 4); + pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); + pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); + pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); + pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); + pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); + NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); + FLTAnEnumBox *aNullableEnum = + aNullableEnumAsNumber == nil + ? nil + : [[FLTAnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; + pigeonResult.aNullableEnum = aNullableEnum; + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); + pigeonResult.allNullableTypes = + [FLTAllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 16))]; + return pigeonResult; +} ++ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list { + return (list) ? [FLTAllNullableTypes fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + self.aNullableBool ?: [NSNull null], + self.aNullableInt ?: [NSNull null], + self.aNullableInt64 ?: [NSNull null], + self.aNullableDouble ?: [NSNull null], + self.aNullableByteArray ?: [NSNull null], + self.aNullable4ByteArray ?: [NSNull null], + self.aNullable8ByteArray ?: [NSNull null], + self.aNullableFloatArray ?: [NSNull null], + self.aNullableList ?: [NSNull null], + self.aNullableMap ?: [NSNull null], + self.nullableNestedList ?: [NSNull null], + self.nullableMapWithAnnotations ?: [NSNull null], + self.nullableMapWithObject ?: [NSNull null], + (self.aNullableEnum == nil ? [NSNull null] + : [NSNumber numberWithInteger:self.aNullableEnum.value]), + self.aNullableString ?: [NSNull null], + self.aNullableObject ?: [NSNull null], + (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + ]; +} +@end + +@implementation FLTAllNullableTypesWithoutRecursion + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullableInt:(nullable NSNumber *)aNullableInt aNullableInt64:(nullable NSNumber *)aNullableInt64 @@ -158,10 +257,11 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject - aNullableEnum:(nullable AnEnumBox *)aNullableEnum + aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject { - AllNullableTypes *pigeonResult = [[AllNullableTypes alloc] init]; + FLTAllNullableTypesWithoutRecursion *pigeonResult = + [[FLTAllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = aNullableBool; pigeonResult.aNullableInt = aNullableInt; pigeonResult.aNullableInt64 = aNullableInt64; @@ -180,8 +280,9 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullableObject = aNullableObject; return pigeonResult; } -+ (AllNullableTypes *)fromList:(NSArray *)list { - AllNullableTypes *pigeonResult = [[AllNullableTypes alloc] init]; ++ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { + FLTAllNullableTypesWithoutRecursion *pigeonResult = + [[FLTAllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); pigeonResult.aNullableInt64 = GetNullableObjectAtIndex(list, 2); @@ -196,17 +297,17 @@ + (AllNullableTypes *)fromList:(NSArray *)list { pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); - AnEnumBox *aNullableEnum = + FLTAnEnumBox *aNullableEnum = aNullableEnumAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; + : [[FLTAnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; pigeonResult.aNullableEnum = aNullableEnum; pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); return pigeonResult; } -+ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { - return (list) ? [AllNullableTypes fromList:list] : nil; ++ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { + return (list) ? [FLTAllNullableTypesWithoutRecursion fromList:list] : nil; } - (NSArray *)toList { return @[ @@ -231,45 +332,52 @@ - (NSArray *)toList { } @end -@implementation AllClassesWrapper -+ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes - allTypes:(nullable AllTypes *)allTypes { - AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; +@implementation FLTAllClassesWrapper ++ (instancetype)makeWithAllNullableTypes:(FLTAllNullableTypes *)allNullableTypes + allNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)allNullableTypesWithoutRecursion + allTypes:(nullable FLTAllTypes *)allTypes { + FLTAllClassesWrapper *pigeonResult = [[FLTAllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = allNullableTypes; + pigeonResult.allNullableTypesWithoutRecursion = allNullableTypesWithoutRecursion; pigeonResult.allTypes = allTypes; return pigeonResult; } -+ (AllClassesWrapper *)fromList:(NSArray *)list { - AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; ++ (FLTAllClassesWrapper *)fromList:(NSArray *)list { + FLTAllClassesWrapper *pigeonResult = [[FLTAllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = - [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))]; + [FLTAllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; + pigeonResult.allNullableTypesWithoutRecursion = + [FLTAllNullableTypesWithoutRecursion nullableFromList:(GetNullableObjectAtIndex(list, 1))]; + pigeonResult.allTypes = [FLTAllTypes nullableFromList:(GetNullableObjectAtIndex(list, 2))]; return pigeonResult; } -+ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list { - return (list) ? [AllClassesWrapper fromList:list] : nil; ++ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list { + return (list) ? [FLTAllClassesWrapper fromList:list] : nil; } - (NSArray *)toList { return @[ (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + (self.allNullableTypesWithoutRecursion ? [self.allNullableTypesWithoutRecursion toList] + : [NSNull null]), (self.allTypes ? [self.allTypes toList] : [NSNull null]), ]; } @end -@implementation TestMessage +@implementation FLTTestMessage + (instancetype)makeWithTestList:(nullable NSArray *)testList { - TestMessage *pigeonResult = [[TestMessage alloc] init]; + FLTTestMessage *pigeonResult = [[FLTTestMessage alloc] init]; pigeonResult.testList = testList; return pigeonResult; } -+ (TestMessage *)fromList:(NSArray *)list { - TestMessage *pigeonResult = [[TestMessage alloc] init]; ++ (FLTTestMessage *)fromList:(NSArray *)list { + FLTTestMessage *pigeonResult = [[FLTTestMessage alloc] init]; pigeonResult.testList = GetNullableObjectAtIndex(list, 0); return pigeonResult; } -+ (nullable TestMessage *)nullableFromList:(NSArray *)list { - return (list) ? [TestMessage fromList:list] : nil; ++ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list { + return (list) ? [FLTTestMessage fromList:list] : nil; } - (NSArray *)toList { return @[ @@ -278,81 +386,86 @@ - (NSArray *)toList { } @end -@interface HostIntegrationCoreApiCodecReader : FlutterStandardReader +@interface FLTHostIntegrationCoreApiCodecReader : FlutterStandardReader @end -@implementation HostIntegrationCoreApiCodecReader +@implementation FLTHostIntegrationCoreApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: - return [AllClassesWrapper fromList:[self readValue]]; + return [FLTAllClassesWrapper fromList:[self readValue]]; case 129: - return [AllNullableTypes fromList:[self readValue]]; + return [FLTAllNullableTypes fromList:[self readValue]]; case 130: - return [AllTypes fromList:[self readValue]]; + return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]]; case 131: - return [TestMessage fromList:[self readValue]]; + return [FLTAllTypes fromList:[self readValue]]; + case 132: + return [FLTTestMessage fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end -@interface HostIntegrationCoreApiCodecWriter : FlutterStandardWriter +@interface FLTHostIntegrationCoreApiCodecWriter : FlutterStandardWriter @end -@implementation HostIntegrationCoreApiCodecWriter +@implementation FLTHostIntegrationCoreApiCodecWriter - (void)writeValue:(id)value { - if ([value isKindOfClass:[AllClassesWrapper class]]) { + if ([value isKindOfClass:[FLTAllClassesWrapper class]]) { [self writeByte:128]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypes class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { + } else if ([value isKindOfClass:[FLTAllTypes class]]) { [self writeByte:131]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FLTTestMessage class]]) { + [self writeByte:132]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end -@interface HostIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter +@interface FLTHostIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation HostIntegrationCoreApiCodecReaderWriter +@implementation FLTHostIntegrationCoreApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[HostIntegrationCoreApiCodecWriter alloc] initWithData:data]; + return [[FLTHostIntegrationCoreApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[HostIntegrationCoreApiCodecReader alloc] initWithData:data]; + return [[FLTHostIntegrationCoreApiCodecReader alloc] initWithData:data]; } @end -NSObject *HostIntegrationCoreApiGetCodec(void) { +NSObject *FLTHostIntegrationCoreApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - HostIntegrationCoreApiCodecReaderWriter *readerWriter = - [[HostIntegrationCoreApiCodecReaderWriter alloc] init]; + FLTHostIntegrationCoreApiCodecReaderWriter *readerWriter = + [[FLTHostIntegrationCoreApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -void SetUpHostIntegrationCoreApi(id binaryMessenger, - NSObject *api) { +void SetUpFLTHostIntegrationCoreApi(id binaryMessenger, + NSObject *api) { /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.noop" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(noopWithError:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(noopWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; @@ -369,17 +482,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoAllTypes:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAllTypes:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAllTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; - AllTypes *output = [api echoAllTypes:arg_everything error:&error]; + FLTAllTypes *output = [api echoAllTypes:arg_everything error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -392,11 +505,11 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.throwError" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(throwErrorWithError:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(throwErrorWithError:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(throwErrorWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; @@ -413,10 +526,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"throwErrorFromVoid" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwErrorFromVoidWithError:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(throwErrorFromVoidWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -434,10 +547,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"throwFlutterError" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwFlutterErrorWithError:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(throwFlutterErrorWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -455,10 +568,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoInt:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoInt:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -477,11 +590,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(echoDouble:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoDouble:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(echoDouble:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoDouble:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; @@ -499,10 +613,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoBool:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoBool:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoBool:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -521,11 +635,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(echoString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoString:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(echoString:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoString:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); @@ -543,11 +658,11 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoUint8List:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoUint8List:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -566,11 +681,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoObject" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(echoObject:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoObject:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(echoObject:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoObject:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); @@ -588,10 +704,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoList:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoList:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoList:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -610,10 +726,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoMap:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoMap:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoMap:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; @@ -632,17 +748,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoClassWrapper" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoClassWrapper:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoClassWrapper:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoClassWrapper:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoClassWrapper:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); + FLTAllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; - AllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error]; + FLTAllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -655,16 +771,16 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoEnum:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoEnum:error:)", + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoEnum:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; - AnEnumBox *enumBox = [api echoEnum:arg_anEnum error:&error]; + FLTAnEnumBox *enumBox = [api echoEnum:arg_anEnum error:&error]; NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; callback(wrapResult(output, error)); }]; @@ -678,10 +794,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNamedDefaultString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedDefaultString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedDefaultString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -701,10 +817,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoOptionalDefaultDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalDefaultDouble:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalDefaultDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -724,12 +840,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoRequiredInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoRequiredInt:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoRequiredInt:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoRequiredInt:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoRequiredInt:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; @@ -747,17 +863,41 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAllNullableTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAllNullableTypes:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAllNullableTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + FLTAllNullableTypes *output = [api echoAllNullableTypes:arg_everything error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the passed object, to test serialization and deserialization. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"echoAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:FLTHostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(echoAllNullableTypesWithoutRecursion:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAllNullableTypesWithoutRecursion:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; - AllNullableTypes *output = [api echoAllNullableTypes:arg_everything error:&error]; + FLTAllNullableTypesWithoutRecursion *output = + [api echoAllNullableTypesWithoutRecursion:arg_everything error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -771,15 +911,15 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"extractNestedNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(extractNestedNullableStringFrom:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(extractNestedNullableStringFrom:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); + FLTAllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api extractNestedNullableStringFrom:arg_wrapper error:&error]; callback(wrapResult(output, error)); @@ -795,18 +935,18 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"createNestedNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createNestedObjectWithNullableString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(createNestedObjectWithNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_nullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; - AllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString - error:&error]; + FLTAllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString + error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -819,11 +959,11 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"sendMultipleNullableTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(sendMultipleNullableTypesABool: anInt:aString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(sendMultipleNullableTypesABool:anInt:aString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -832,10 +972,40 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); FlutterError *error; - AllNullableTypes *output = [api sendMultipleNullableTypesABool:arg_aNullableBool - anInt:arg_aNullableInt - aString:arg_aNullableString - error:&error]; + FLTAllNullableTypes *output = [api sendMultipleNullableTypesABool:arg_aNullableBool + anInt:arg_aNullableInt + aString:arg_aNullableString + error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns passed in arguments of multiple types. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"sendMultipleNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:FLTHostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); + NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); + NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); + FlutterError *error; + FLTAllNullableTypesWithoutRecursion *output = + [api sendMultipleNullableTypesWithoutRecursionABool:arg_aNullableBool + anInt:arg_aNullableInt + aString:arg_aNullableString + error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -848,12 +1018,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoNullableInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoNullableInt:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableInt:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoNullableInt:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoNullableInt:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 0); @@ -871,10 +1041,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableDouble:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -894,12 +1064,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoNullableBool:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableBool:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoNullableBool:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoNullableBool:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); @@ -917,10 +1087,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -940,10 +1110,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableUint8List:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -964,10 +1134,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableObject" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableObject:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableObject:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -987,12 +1157,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoNullableList:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableList:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoNullableList:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoNullableList:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_aNullableList = GetNullableObjectAtIndex(args, 0); @@ -1010,12 +1180,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoNullableMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoNullableMap:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableMap:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoNullableMap:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoNullableMap:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSDictionary *arg_aNullableMap = GetNullableObjectAtIndex(args, 0); @@ -1032,21 +1202,21 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNullableEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoNullableEnum:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableEnum:error:)", - api); + NSCAssert([api respondsToSelector:@selector(echoNullableEnum:error:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoNullableEnum:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = + FLTAnEnumBox *arg_anEnum = arg_anEnumAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; FlutterError *error; - AnEnumBox *enumBox = [api echoNullableEnum:arg_anEnum error:&error]; + FLTAnEnumBox *enumBox = [api echoNullableEnum:arg_anEnum error:&error]; NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; callback(wrapResult(output, error)); }]; @@ -1060,10 +1230,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoOptionalNullableInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalNullableInt:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalNullableInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1083,10 +1253,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoNamedNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedNullableString:error:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1107,12 +1277,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.noopAsync" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(noopAsyncWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(noopAsyncWithCompletion:)", - api); + NSCAssert([api respondsToSelector:@selector(noopAsyncWithCompletion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(noopAsyncWithCompletion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { [api noopAsyncWithCompletion:^(FlutterError *_Nullable error) { callback(wrapResult(nil, error)); @@ -1128,12 +1298,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoAsyncInt:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAsyncInt:completion:)", - api); + NSCAssert([api respondsToSelector:@selector(echoAsyncInt:completion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAsyncInt:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; @@ -1152,10 +1322,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncDouble:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1176,10 +1346,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncBool:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1200,10 +1370,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncString:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1224,10 +1394,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncUint8List:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1249,10 +1419,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncObject" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncObject:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1273,10 +1443,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncList:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1297,12 +1467,12 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(echoAsyncMap:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAsyncMap:completion:)", - api); + NSCAssert([api respondsToSelector:@selector(echoAsyncMap:completion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAsyncMap:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); @@ -1322,17 +1492,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncEnum:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; [api echoAsyncEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { + completion:^(FLTAnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { NSNumber *output = enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; callback(wrapResult(output, error)); @@ -1348,10 +1518,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.throwAsyncError" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(throwAsyncErrorWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1369,10 +1539,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"throwAsyncErrorFromVoid" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorFromVoidWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(throwAsyncErrorFromVoidWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1390,10 +1560,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"throwAsyncFlutterError" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncFlutterErrorWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(throwAsyncFlutterErrorWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1412,17 +1582,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncAllTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncAllTypes:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncAllTypes:arg_everything - completion:^(AllTypes *_Nullable output, FlutterError *_Nullable error) { + completion:^(FLTAllTypes *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -1436,17 +1606,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableAllNullableTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableAllNullableTypes:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableAllNullableTypes:arg_everything - completion:^(AllNullableTypes *_Nullable output, + completion:^(FLTAllNullableTypes *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1455,16 +1625,43 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Returns the passed object, to test serialization and deserialization. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"echoAsyncNullableAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:FLTHostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); + [api echoAsyncNullableAllNullableTypesWithoutRecursion:arg_everything + completion:^(FLTAllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } /// Returns passed in int asynchronously. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableInt:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1485,10 +1682,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableDouble:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1509,10 +1706,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableBool:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1533,10 +1730,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableString:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1557,10 +1754,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableUint8List:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1582,10 +1779,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableObject" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableObject:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1606,10 +1803,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableList:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1630,10 +1827,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableMap:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1655,26 +1852,26 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"echoAsyncNullableEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableEnum:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = + FLTAnEnumBox *arg_anEnum = arg_anEnumAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; - [api - echoAsyncNullableEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; - callback(wrapResult(output, error)); - }]; + : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + [api echoAsyncNullableEnum:arg_anEnum + completion:^(FLTAnEnumBox *_Nullable enumValue, + FlutterError *_Nullable error) { + NSNumber *output = + enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; + callback(wrapResult(output, error)); + }]; }]; } else { [channel setMessageHandler:nil]; @@ -1685,10 +1882,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName: @"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterNoop" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterNoopWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterNoopWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1705,10 +1902,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterThrowError" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterThrowErrorWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1726,10 +1923,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterThrowErrorFromVoid" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorFromVoidWithCompletion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterThrowErrorFromVoidWithCompletion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1746,17 +1943,18 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoAllTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllTypes:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllTypes:arg_everything - completion:^(AllTypes *_Nullable output, FlutterError *_Nullable error) { + completion:^(FLTAllTypes *_Nullable output, + FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -1769,17 +1967,17 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoAllNullableTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllNullableTypes:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); + FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllNullableTypes:arg_everything - completion:^(AllNullableTypes *_Nullable output, + completion:^(FLTAllNullableTypes *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1793,11 +1991,11 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterSendMultipleNullableTypes" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1808,7 +2006,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [api callFlutterSendMultipleNullableTypesABool:arg_aNullableBool anInt:arg_aNullableInt aString:arg_aNullableString - completion:^(AllNullableTypes *_Nullable output, + completion:^(FLTAllNullableTypes *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1817,15 +2015,75 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"callFlutterEchoAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:FLTHostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (callFlutterEchoAllNullableTypesWithoutRecursion:completion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(callFlutterEchoAllNullableTypesWithoutRecursion:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoAllNullableTypesWithoutRecursion:arg_everything + completion:^(FLTAllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"callFlutterSendMultipleNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:FLTHostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector + (callFlutterSendMultipleNullableTypesWithoutRecursionABool: + anInt:aString:completion:)], + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(callFlutterSendMultipleNullableTypesWithoutRecursionABool:anInt:aString:" + @"completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); + NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); + NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); + [api callFlutterSendMultipleNullableTypesWithoutRecursionABool:arg_aNullableBool + anInt:arg_aNullableInt + aString:arg_aNullableString + completion:^( + FLTAllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoBool:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1845,10 +2103,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoInt:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1868,10 +2126,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoDouble:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1891,10 +2149,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoString:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1914,10 +2172,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoUint8List:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1938,10 +2196,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoList:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1961,10 +2219,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoMap:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -1985,17 +2243,18 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoEnum:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; [api callFlutterEchoEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { + completion:^(FLTAnEnumBox *_Nullable enumValue, + FlutterError *_Nullable error) { NSNumber *output = enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; callback(wrapResult(output, error)); @@ -2010,10 +2269,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableBool" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableBool:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2034,10 +2293,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableInt" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableInt:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2058,10 +2317,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableDouble" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableDouble:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2082,10 +2341,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableString" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableString:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2106,10 +2365,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableUint8List" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableUint8List:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2130,10 +2389,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableList" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableList:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2154,10 +2413,10 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableMap" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableMap:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { @@ -2178,21 +2437,21 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @"callFlutterEchoNullableEnum" binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:FLTHostIntegrationCoreApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableEnum:completion:)], - @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = + FLTAnEnumBox *arg_anEnum = arg_anEnumAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; [api callFlutterEchoNullableEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, + completion:^(FLTAnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { NSNumber *output = enumValue == nil ? nil @@ -2205,74 +2464,79 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, } } } -@interface FlutterIntegrationCoreApiCodecReader : FlutterStandardReader +@interface FLTFlutterIntegrationCoreApiCodecReader : FlutterStandardReader @end -@implementation FlutterIntegrationCoreApiCodecReader +@implementation FLTFlutterIntegrationCoreApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: - return [AllClassesWrapper fromList:[self readValue]]; + return [FLTAllClassesWrapper fromList:[self readValue]]; case 129: - return [AllNullableTypes fromList:[self readValue]]; + return [FLTAllNullableTypes fromList:[self readValue]]; case 130: - return [AllTypes fromList:[self readValue]]; + return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]]; case 131: - return [TestMessage fromList:[self readValue]]; + return [FLTAllTypes fromList:[self readValue]]; + case 132: + return [FLTTestMessage fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end -@interface FlutterIntegrationCoreApiCodecWriter : FlutterStandardWriter +@interface FLTFlutterIntegrationCoreApiCodecWriter : FlutterStandardWriter @end -@implementation FlutterIntegrationCoreApiCodecWriter +@implementation FLTFlutterIntegrationCoreApiCodecWriter - (void)writeValue:(id)value { - if ([value isKindOfClass:[AllClassesWrapper class]]) { + if ([value isKindOfClass:[FLTAllClassesWrapper class]]) { [self writeByte:128]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypes class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { + } else if ([value isKindOfClass:[FLTAllTypes class]]) { [self writeByte:131]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FLTTestMessage class]]) { + [self writeByte:132]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end -@interface FlutterIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter +@interface FLTFlutterIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation FlutterIntegrationCoreApiCodecReaderWriter +@implementation FLTFlutterIntegrationCoreApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FlutterIntegrationCoreApiCodecWriter alloc] initWithData:data]; + return [[FLTFlutterIntegrationCoreApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FlutterIntegrationCoreApiCodecReader alloc] initWithData:data]; + return [[FLTFlutterIntegrationCoreApiCodecReader alloc] initWithData:data]; } @end -NSObject *FlutterIntegrationCoreApiGetCodec(void) { +NSObject *FLTFlutterIntegrationCoreApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - FlutterIntegrationCoreApiCodecReaderWriter *readerWriter = - [[FlutterIntegrationCoreApiCodecReaderWriter alloc] init]; + FLTFlutterIntegrationCoreApiCodecReaderWriter *readerWriter = + [[FLTFlutterIntegrationCoreApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -@interface FlutterIntegrationCoreApi () +@interface FLTFlutterIntegrationCoreApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end -@implementation FlutterIntegrationCoreApi +@implementation FLTFlutterIntegrationCoreApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; @@ -2287,7 +2551,7 @@ - (void)noopWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2309,7 +2573,7 @@ - (void)throwErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2332,7 +2596,7 @@ - (void)throwErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))comp FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2348,14 +2612,14 @@ - (void)throwErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))comp } }]; } -- (void)echoAllTypes:(AllTypes *)arg_everything - completion:(void (^)(AllTypes *_Nullable, FlutterError *_Nullable))completion { +- (void)echoAllTypes:(FLTAllTypes *)arg_everything + completion:(void (^)(FLTAllTypes *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllTypes"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2364,7 +2628,7 @@ - (void)echoAllTypes:(AllTypes *)arg_everything message:reply[1] details:reply[2]]); } else { - AllTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; + FLTAllTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -2372,15 +2636,15 @@ - (void)echoAllTypes:(AllTypes *)arg_everything } }]; } -- (void)echoAllNullableTypes:(nullable AllNullableTypes *)arg_everything - completion: - (void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion { +- (void)echoAllNullableTypes:(nullable FLTAllNullableTypes *)arg_everything + completion:(void (^)(FLTAllNullableTypes *_Nullable, + FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypes"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2389,7 +2653,7 @@ - (void)echoAllNullableTypes:(nullable AllNullableTypes *)arg_everything message:reply[1] details:reply[2]]); } else { - AllNullableTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; + FLTAllNullableTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -2400,14 +2664,74 @@ - (void)echoAllNullableTypes:(nullable AllNullableTypes *)arg_everything - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool anInt:(nullable NSNumber *)arg_aNullableInt aString:(nullable NSString *)arg_aNullableString - completion:(void (^)(AllNullableTypes *_Nullable, + completion:(void (^)(FLTAllNullableTypes *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." @"sendMultipleNullableTypes"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; + [channel sendMessage:@[ + arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], + arg_aNullableString ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + FLTAllNullableTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, createConnectionError(channelName)); + } + }]; +} +- (void)echoAllNullableTypesWithoutRecursion: + (nullable FLTAllNullableTypesWithoutRecursion *)arg_everything + completion: + (void (^)(FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + @"echoAllNullableTypesWithoutRecursion"; + FlutterBasicMessageChannel *channel = + [FlutterBasicMessageChannel messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FLTFlutterIntegrationCoreApiGetCodec()]; + [channel sendMessage:@[ arg_everything ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + FLTAllNullableTypesWithoutRecursion *output = + reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, createConnectionError(channelName)); + } + }]; +} +- (void)sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)arg_aNullableBool + anInt:(nullable NSNumber *)arg_aNullableInt + aString:(nullable NSString *)arg_aNullableString + completion: + (void (^)( + FLTAllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + @"sendMultipleNullableTypesWithoutRecursion"; + FlutterBasicMessageChannel *channel = + [FlutterBasicMessageChannel messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], arg_aNullableString ?: [NSNull null] @@ -2419,7 +2743,8 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool message:reply[1] details:reply[2]]); } else { - AllNullableTypes *output = reply[0] == [NSNull null] ? nil : reply[0]; + FLTAllNullableTypesWithoutRecursion *output = + reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -2434,7 +2759,7 @@ - (void)echoBool:(BOOL)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ @(arg_aBool) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2458,7 +2783,7 @@ - (void)echoInt:(NSInteger)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ @(arg_anInt) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2482,7 +2807,7 @@ - (void)echoDouble:(double)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ @(arg_aDouble) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2506,7 +2831,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2531,7 +2856,7 @@ - (void)echoUint8List:(FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aList ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2556,7 +2881,7 @@ - (void)echoList:(NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aList ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2581,7 +2906,7 @@ - (void)echoMap:(NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2599,14 +2924,14 @@ - (void)echoMap:(NSDictionary *)arg_aMap } }]; } -- (void)echoEnum:(AnEnum)arg_anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { +- (void)echoEnum:(FLTAnEnum)arg_anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ [NSNumber numberWithInteger:arg_anEnum] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2616,10 +2941,10 @@ - (void)echoEnum:(AnEnum)arg_anEnum details:reply[2]]); } else { NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - AnEnumBox *output = + FLTAnEnumBox *output = outputAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + : [[FLTAnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; completion(output, nil); } } else { @@ -2634,7 +2959,7 @@ - (void)echoNullableBool:(nullable NSNumber *)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aBool ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2658,7 +2983,7 @@ - (void)echoNullableInt:(nullable NSNumber *)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_anInt ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2682,7 +3007,7 @@ - (void)echoNullableDouble:(nullable NSNumber *)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aDouble ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2706,7 +3031,7 @@ - (void)echoNullableString:(nullable NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2731,7 +3056,7 @@ - (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aList ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2756,7 +3081,7 @@ - (void)echoNullableList:(nullable NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aList ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2781,7 +3106,7 @@ - (void)echoNullableMap:(nullable NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2799,14 +3124,14 @@ - (void)echoNullableMap:(nullable NSDictionary *)arg_aMap } }]; } -- (void)echoNullableEnum:(nullable AnEnumBox *)arg_anEnum - completion:(void (^)(AnEnumBox *_Nullable, FlutterError *_Nullable))completion { +- (void)echoNullableEnum:(nullable FLTAnEnumBox *)arg_anEnum + completion:(void (^)(FLTAnEnumBox *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableEnum"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_anEnum == nil ? [NSNull null] : [NSNumber numberWithInteger:arg_anEnum.value] ] reply:^(NSArray *reply) { @@ -2817,10 +3142,10 @@ - (void)echoNullableEnum:(nullable AnEnumBox *)arg_anEnum details:reply[2]]); } else { NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - AnEnumBox *output = + FLTAnEnumBox *output = outputAsNumber == nil ? nil - : [[AnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + : [[FLTAnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; completion(output, nil); } } else { @@ -2834,7 +3159,7 @@ - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2857,7 +3182,7 @@ - (void)echoAsyncString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:FLTFlutterIntegrationCoreApiGetCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2876,22 +3201,22 @@ - (void)echoAsyncString:(NSString *)arg_aString } @end -NSObject *HostTrivialApiGetCodec(void) { +NSObject *FLTHostTrivialApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } -void SetUpHostTrivialApi(id binaryMessenger, - NSObject *api) { +void SetUpFLTHostTrivialApi(id binaryMessenger, + NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop" binaryMessenger:binaryMessenger - codec:HostTrivialApiGetCodec()]; + codec:FLTHostTrivialApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], - @"HostTrivialApi api (%@) doesn't respond to @selector(noopWithError:)", api); + @"FLTHostTrivialApi api (%@) doesn't respond to @selector(noopWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api noopWithError:&error]; @@ -2902,21 +3227,23 @@ void SetUpHostTrivialApi(id binaryMessenger, } } } -NSObject *HostSmallApiGetCodec(void) { +NSObject *FLTHostSmallApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } -void SetUpHostSmallApi(id binaryMessenger, NSObject *api) { +void SetUpFLTHostSmallApi(id binaryMessenger, + NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo" binaryMessenger:binaryMessenger - codec:HostSmallApiGetCodec()]; + codec:FLTHostSmallApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoString:completion:)], - @"HostSmallApi api (%@) doesn't respond to @selector(echoString:completion:)", api); + @"FLTHostSmallApi api (%@) doesn't respond to @selector(echoString:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); @@ -2933,10 +3260,10 @@ void SetUpHostSmallApi(id binaryMessenger, NSObject binaryMessenger, NSObject *FlutterSmallApiGetCodec(void) { +NSObject *FLTFlutterSmallApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - FlutterSmallApiCodecReaderWriter *readerWriter = - [[FlutterSmallApiCodecReaderWriter alloc] init]; + FLTFlutterSmallApiCodecReaderWriter *readerWriter = + [[FLTFlutterSmallApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -@interface FlutterSmallApi () +@interface FLTFlutterSmallApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end -@implementation FlutterSmallApi +@implementation FLTFlutterSmallApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; @@ -3009,14 +3336,14 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)echoWrappedList:(TestMessage *)arg_msg - completion:(void (^)(TestMessage *_Nullable, FlutterError *_Nullable))completion { +- (void)echoWrappedList:(FLTTestMessage *)arg_msg + completion:(void (^)(FLTTestMessage *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoWrappedList"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterSmallApiGetCodec()]; + codec:FLTFlutterSmallApiGetCodec()]; [channel sendMessage:@[ arg_msg ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3025,7 +3352,7 @@ - (void)echoWrappedList:(TestMessage *)arg_msg message:reply[1] details:reply[2]]); } else { - TestMessage *output = reply[0] == [NSNull null] ? nil : reply[0]; + FLTTestMessage *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -3039,7 +3366,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterSmallApiGetCodec()]; + codec:FLTFlutterSmallApiGetCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m index e9d679f42151..ea91a7eaa1b1 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m @@ -10,9 +10,7 @@ @interface AlternateLanguageTestPlugin () @property(nonatomic) FlutterIntegrationCoreApi *flutterAPI; @end -/** - * This plugin handles the native side of the integration tests in example/integration_test/. - */ +/// This plugin handles the native side of the integration tests in example/integration_test/. @implementation AlternateLanguageTestPlugin + (void)registerWithRegistrar:(NSObject *)registrar { AlternateLanguageTestPlugin *plugin = [[AlternateLanguageTestPlugin alloc] init]; @@ -36,6 +34,12 @@ - (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes * return everything; } +- (nullable AllNullableTypesWithoutRecursion *) + echoAllNullableTypesWithoutRecursion:(nullable AllNullableTypesWithoutRecursion *)everything + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + return everything; +} + - (nullable id)throwErrorWithError:(FlutterError *_Nullable *_Nonnull)error { *error = [FlutterError errorWithCode:@"An error" message:nil details:nil]; return nil; @@ -94,6 +98,7 @@ - (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper - (AnEnumBox *_Nullable)echoEnum:(AnEnum)anEnum error:(FlutterError *_Nullable *_Nonnull)error { return [[AnEnumBox alloc] initWithValue:anEnum]; } + - (nullable NSString *)echoNamedDefaultString:(NSString *)aString error:(FlutterError *_Nullable *_Nonnull)error { return aString; @@ -119,7 +124,9 @@ - (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrap error:(FlutterError *_Nullable *_Nonnull)error { AllNullableTypes *innerObject = [[AllNullableTypes alloc] init]; innerObject.aNullableString = nullableString; - return [AllClassesWrapper makeWithAllNullableTypes:innerObject allTypes:nil]; + return [AllClassesWrapper makeWithAllNullableTypes:innerObject + allNullableTypesWithoutRecursion:nil + allTypes:nil]; } - (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool @@ -134,6 +141,20 @@ - (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber return someTypes; } +- (nullable AllNullableTypesWithoutRecursion *) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error: + (FlutterError *_Nullable __autoreleasing *_Nonnull) + error { + AllNullableTypesWithoutRecursion *someTypes = [[AllNullableTypesWithoutRecursion alloc] init]; + someTypes.aNullableBool = aNullableBool; + someTypes.aNullableInt = aNullableInt; + someTypes.aNullableString = aNullableString; + return someTypes; +} + - (nullable NSNumber *)echoNullableInt:(nullable NSNumber *)aNullableInt error:(FlutterError *_Nullable *_Nonnull)error { return aNullableInt; @@ -219,6 +240,15 @@ - (void)echoAsyncNullableAllNullableTypes:(nullable AllNullableTypes *)everythin completion(everything, nil); } +- (void)echoAsyncNullableAllNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)everything + completion: + (nonnull void (^)( + AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + completion(everything, nil); +} + - (void)echoAsyncInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { completion(@(anInt), nil); @@ -354,6 +384,25 @@ - (void)callFlutterSendMultipleNullableTypesABool:(nullable NSNumber *)aNullable }]; } +- (void)callFlutterSendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString: + (nullable NSString *)aNullableString + completion: + (nonnull void (^)( + AllNullableTypesWithoutRecursion + *_Nullable, + FlutterError *_Nullable))completion { + [self.flutterAPI + sendMultipleNullableTypesWithoutRecursionABool:aNullableBool + anInt:aNullableInt + aString:aNullableString + completion:^(AllNullableTypesWithoutRecursion *value, + FlutterError *error) { + completion(value, error); + }]; +} + - (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoBool:aBool @@ -429,6 +478,19 @@ - (void)callFlutterEchoAllNullableTypes:(nullable AllNullableTypes *)everything }]; } +- (void)callFlutterEchoAllNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)everything + completion: + (nonnull void (^)( + AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + [self.flutterAPI echoAllNullableTypesWithoutRecursion:everything + completion:^(AllNullableTypesWithoutRecursion *value, + FlutterError *error) { + completion(value, error); + }]; +} + - (void)callFlutterEchoNullableBool:(nullable NSNumber *)aBool completion: (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h index e7022baa9403..a7ec37232c18 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @class AllTypes; @class AllNullableTypes; +@class AllNullableTypesWithoutRecursion; @class AllClassesWrapper; @class TestMessage; @@ -67,6 +68,48 @@ typedef NS_ENUM(NSUInteger, AnEnum) { /// A class containing all supported nullable types. @interface AllNullableTypes : NSObject ++ (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool + aNullableInt:(nullable NSNumber *)aNullableInt + aNullableInt64:(nullable NSNumber *)aNullableInt64 + aNullableDouble:(nullable NSNumber *)aNullableDouble + aNullableByteArray:(nullable FlutterStandardTypedData *)aNullableByteArray + aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray + aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray + aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray + aNullableList:(nullable NSArray *)aNullableList + aNullableMap:(nullable NSDictionary *)aNullableMap + nullableNestedList:(nullable NSArray *> *)nullableNestedList + nullableMapWithAnnotations: + (nullable NSDictionary *)nullableMapWithAnnotations + nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject + aNullableEnum:(nullable AnEnumBox *)aNullableEnum + aNullableString:(nullable NSString *)aNullableString + aNullableObject:(nullable id)aNullableObject + allNullableTypes:(nullable AllNullableTypes *)allNullableTypes; +@property(nonatomic, strong, nullable) NSNumber *aNullableBool; +@property(nonatomic, strong, nullable) NSNumber *aNullableInt; +@property(nonatomic, strong, nullable) NSNumber *aNullableInt64; +@property(nonatomic, strong, nullable) NSNumber *aNullableDouble; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable4ByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable8ByteArray; +@property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableFloatArray; +@property(nonatomic, copy, nullable) NSArray *aNullableList; +@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; +@property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; +@property(nonatomic, copy, nullable) + NSDictionary *nullableMapWithAnnotations; +@property(nonatomic, copy, nullable) NSDictionary *nullableMapWithObject; +@property(nonatomic, strong, nullable) AnEnumBox *aNullableEnum; +@property(nonatomic, copy, nullable) NSString *aNullableString; +@property(nonatomic, strong, nullable) id aNullableObject; +@property(nonatomic, strong, nullable) AllNullableTypes *allNullableTypes; +@end + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +@interface AllNullableTypesWithoutRecursion : NSObject + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullableInt:(nullable NSNumber *)aNullableInt aNullableInt64:(nullable NSNumber *)aNullableInt64 @@ -112,8 +155,12 @@ typedef NS_ENUM(NSUInteger, AnEnum) { /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes + allNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)allNullableTypesWithoutRecursion allTypes:(nullable AllTypes *)allTypes; @property(nonatomic, strong) AllNullableTypes *allNullableTypes; +@property(nonatomic, strong, nullable) + AllNullableTypesWithoutRecursion *allNullableTypesWithoutRecursion; @property(nonatomic, strong, nullable) AllTypes *allTypes; @end @@ -206,6 +253,10 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// Returns the passed object, to test serialization and deserialization. - (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes *)everything error:(FlutterError *_Nullable *_Nonnull)error; +/// Returns the passed object, to test serialization and deserialization. +- (nullable AllNullableTypesWithoutRecursion *) + echoAllNullableTypesWithoutRecursion:(nullable AllNullableTypesWithoutRecursion *)everything + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. - (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper @@ -225,6 +276,14 @@ NSObject *HostIntegrationCoreApiGetCodec(void); aString:(nullable NSString *)aNullableString error:(FlutterError *_Nullable *_Nonnull) error; +/// Returns passed in arguments of multiple types. +/// +/// @return `nil` only when `error != nil`. +- (nullable AllNullableTypesWithoutRecursion *) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in int. - (nullable NSNumber *)echoNullableInt:(nullable NSNumber *)aNullableInt error:(FlutterError *_Nullable *_Nonnull)error; @@ -305,6 +364,13 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)echoAsyncNullableAllNullableTypes:(nullable AllNullableTypes *)everything completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +/// Returns the passed object, to test serialization and deserialization. +- (void)echoAsyncNullableAllNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)everything + completion: + (void (^)( + AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; /// Returns passed in int asynchronously. - (void)echoAsyncNullableInt:(nullable NSNumber *)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @@ -348,6 +414,21 @@ NSObject *HostIntegrationCoreApiGetCodec(void); aString:(nullable NSString *)aNullableString completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +- (void) + callFlutterEchoAllNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)everything + completion: + (void (^)(AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; +- (void) + callFlutterSendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + completion: + (void (^)(AllNullableTypesWithoutRecursion + *_Nullable, + FlutterError *_Nullable)) + completion; - (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoInt:(NSInteger)anInt @@ -424,6 +505,20 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); aString:(nullable NSString *)aNullableString completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; +/// Returns the passed object, to test serialization and deserialization. +- (void)echoAllNullableTypesWithoutRecursion:(nullable AllNullableTypesWithoutRecursion *)everything + completion:(void (^)(AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; +/// Returns passed in arguments of multiple types. +/// +/// Tests multiple-arity FlutterApi handling. +- (void) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)aNullableBool + anInt:(nullable NSNumber *)aNullableInt + aString:(nullable NSString *)aNullableString + completion: + (void (^)(AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion; /// Returns the passed boolean, to test serialization and deserialization. - (void)echoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m index 0cc5b059c04f..b90345836c5a 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m @@ -62,6 +62,12 @@ + (nullable AllNullableTypes *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface AllNullableTypesWithoutRecursion () ++ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; ++ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface AllClassesWrapper () + (AllClassesWrapper *)fromList:(NSArray *)list; + (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list; @@ -160,7 +166,8 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject aNullableEnum:(nullable AnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString - aNullableObject:(nullable id)aNullableObject { + aNullableObject:(nullable id)aNullableObject + allNullableTypes:(nullable AllNullableTypes *)allNullableTypes { AllNullableTypes *pigeonResult = [[AllNullableTypes alloc] init]; pigeonResult.aNullableBool = aNullableBool; pigeonResult.aNullableInt = aNullableInt; @@ -178,6 +185,7 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullableEnum = aNullableEnum; pigeonResult.aNullableString = aNullableString; pigeonResult.aNullableObject = aNullableObject; + pigeonResult.allNullableTypes = allNullableTypes; return pigeonResult; } + (AllNullableTypes *)fromList:(NSArray *)list { @@ -203,11 +211,102 @@ + (AllNullableTypes *)fromList:(NSArray *)list { pigeonResult.aNullableEnum = aNullableEnum; pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); + pigeonResult.allNullableTypes = + [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 16))]; return pigeonResult; } + (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { return (list) ? [AllNullableTypes fromList:list] : nil; } +- (NSArray *)toList { + return @[ + self.aNullableBool ?: [NSNull null], + self.aNullableInt ?: [NSNull null], + self.aNullableInt64 ?: [NSNull null], + self.aNullableDouble ?: [NSNull null], + self.aNullableByteArray ?: [NSNull null], + self.aNullable4ByteArray ?: [NSNull null], + self.aNullable8ByteArray ?: [NSNull null], + self.aNullableFloatArray ?: [NSNull null], + self.aNullableList ?: [NSNull null], + self.aNullableMap ?: [NSNull null], + self.nullableNestedList ?: [NSNull null], + self.nullableMapWithAnnotations ?: [NSNull null], + self.nullableMapWithObject ?: [NSNull null], + (self.aNullableEnum == nil ? [NSNull null] + : [NSNumber numberWithInteger:self.aNullableEnum.value]), + self.aNullableString ?: [NSNull null], + self.aNullableObject ?: [NSNull null], + (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + ]; +} +@end + +@implementation AllNullableTypesWithoutRecursion ++ (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool + aNullableInt:(nullable NSNumber *)aNullableInt + aNullableInt64:(nullable NSNumber *)aNullableInt64 + aNullableDouble:(nullable NSNumber *)aNullableDouble + aNullableByteArray:(nullable FlutterStandardTypedData *)aNullableByteArray + aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray + aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray + aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray + aNullableList:(nullable NSArray *)aNullableList + aNullableMap:(nullable NSDictionary *)aNullableMap + nullableNestedList:(nullable NSArray *> *)nullableNestedList + nullableMapWithAnnotations: + (nullable NSDictionary *)nullableMapWithAnnotations + nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject + aNullableEnum:(nullable AnEnumBox *)aNullableEnum + aNullableString:(nullable NSString *)aNullableString + aNullableObject:(nullable id)aNullableObject { + AllNullableTypesWithoutRecursion *pigeonResult = [[AllNullableTypesWithoutRecursion alloc] init]; + pigeonResult.aNullableBool = aNullableBool; + pigeonResult.aNullableInt = aNullableInt; + pigeonResult.aNullableInt64 = aNullableInt64; + pigeonResult.aNullableDouble = aNullableDouble; + pigeonResult.aNullableByteArray = aNullableByteArray; + pigeonResult.aNullable4ByteArray = aNullable4ByteArray; + pigeonResult.aNullable8ByteArray = aNullable8ByteArray; + pigeonResult.aNullableFloatArray = aNullableFloatArray; + pigeonResult.aNullableList = aNullableList; + pigeonResult.aNullableMap = aNullableMap; + pigeonResult.nullableNestedList = nullableNestedList; + pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; + pigeonResult.nullableMapWithObject = nullableMapWithObject; + pigeonResult.aNullableEnum = aNullableEnum; + pigeonResult.aNullableString = aNullableString; + pigeonResult.aNullableObject = aNullableObject; + return pigeonResult; +} ++ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { + AllNullableTypesWithoutRecursion *pigeonResult = [[AllNullableTypesWithoutRecursion alloc] init]; + pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); + pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); + pigeonResult.aNullableInt64 = GetNullableObjectAtIndex(list, 2); + pigeonResult.aNullableDouble = GetNullableObjectAtIndex(list, 3); + pigeonResult.aNullableByteArray = GetNullableObjectAtIndex(list, 4); + pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); + pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); + pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); + pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); + pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); + NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); + AnEnumBox *aNullableEnum = + aNullableEnumAsNumber == nil + ? nil + : [[AnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; + pigeonResult.aNullableEnum = aNullableEnum; + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); + return pigeonResult; +} ++ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { + return (list) ? [AllNullableTypesWithoutRecursion fromList:list] : nil; +} - (NSArray *)toList { return @[ self.aNullableBool ?: [NSNull null], @@ -233,9 +332,12 @@ - (NSArray *)toList { @implementation AllClassesWrapper + (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes + allNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)allNullableTypesWithoutRecursion allTypes:(nullable AllTypes *)allTypes { AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = allNullableTypes; + pigeonResult.allNullableTypesWithoutRecursion = allNullableTypesWithoutRecursion; pigeonResult.allTypes = allTypes; return pigeonResult; } @@ -243,7 +345,9 @@ + (AllClassesWrapper *)fromList:(NSArray *)list { AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))]; + pigeonResult.allNullableTypesWithoutRecursion = + [AllNullableTypesWithoutRecursion nullableFromList:(GetNullableObjectAtIndex(list, 1))]; + pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 2))]; return pigeonResult; } + (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list { @@ -252,6 +356,8 @@ + (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list { - (NSArray *)toList { return @[ (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + (self.allNullableTypesWithoutRecursion ? [self.allNullableTypesWithoutRecursion toList] + : [NSNull null]), (self.allTypes ? [self.allTypes toList] : [NSNull null]), ]; } @@ -288,8 +394,10 @@ - (nullable id)readValueOfType:(UInt8)type { case 129: return [AllNullableTypes fromList:[self readValue]]; case 130: - return [AllTypes fromList:[self readValue]]; + return [AllNullableTypesWithoutRecursion fromList:[self readValue]]; case 131: + return [AllTypes fromList:[self readValue]]; + case 132: return [TestMessage fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -307,12 +415,15 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[AllNullableTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { + } else if ([value isKindOfClass:[AllNullableTypesWithoutRecursion class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { + } else if ([value isKindOfClass:[AllTypes class]]) { [self writeByte:131]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[TestMessage class]]) { + [self writeByte:132]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -764,6 +875,30 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Returns the passed object, to test serialization and deserialization. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"echoAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:HostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(echoAllNullableTypesWithoutRecursion:error:)], + @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAllNullableTypesWithoutRecursion:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + AllNullableTypesWithoutRecursion *output = + [api echoAllNullableTypesWithoutRecursion:arg_everything error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. { @@ -842,6 +977,36 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Returns passed in arguments of multiple types. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"sendMultipleNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:HostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)], + @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); + NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); + NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); + FlutterError *error; + AllNullableTypesWithoutRecursion *output = + [api sendMultipleNullableTypesWithoutRecursionABool:arg_aNullableBool + anInt:arg_aNullableInt + aString:arg_aNullableString + error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } /// Returns passed in int. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1455,6 +1620,33 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Returns the passed object, to test serialization and deserialization. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"echoAsyncNullableAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:HostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)], + @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); + [api echoAsyncNullableAllNullableTypesWithoutRecursion:arg_everything + completion:^(AllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } /// Returns passed in int asynchronously. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1817,6 +2009,66 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"callFlutterEchoAllNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:HostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector + (callFlutterEchoAllNullableTypesWithoutRecursion:completion:)], + @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(callFlutterEchoAllNullableTypesWithoutRecursion:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoAllNullableTypesWithoutRecursion:arg_everything + completion:^(AllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + @"callFlutterSendMultipleNullableTypesWithoutRecursion" + binaryMessenger:binaryMessenger + codec:HostIntegrationCoreApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector + (callFlutterSendMultipleNullableTypesWithoutRecursionABool: + anInt:aString:completion:)], + @"HostIntegrationCoreApi api (%@) doesn't respond to " + @"@selector(callFlutterSendMultipleNullableTypesWithoutRecursionABool:anInt:aString:" + @"completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); + NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); + NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); + [api callFlutterSendMultipleNullableTypesWithoutRecursionABool:arg_aNullableBool + anInt:arg_aNullableInt + aString:arg_aNullableString + completion:^( + AllNullableTypesWithoutRecursion + *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." @@ -2215,8 +2467,10 @@ - (nullable id)readValueOfType:(UInt8)type { case 129: return [AllNullableTypes fromList:[self readValue]]; case 130: - return [AllTypes fromList:[self readValue]]; + return [AllNullableTypesWithoutRecursion fromList:[self readValue]]; case 131: + return [AllTypes fromList:[self readValue]]; + case 132: return [TestMessage fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -2234,12 +2488,15 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[AllNullableTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { + } else if ([value isKindOfClass:[AllNullableTypesWithoutRecursion class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { + } else if ([value isKindOfClass:[AllTypes class]]) { [self writeByte:131]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[TestMessage class]]) { + [self writeByte:132]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -2427,6 +2684,66 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool } }]; } +- (void)echoAllNullableTypesWithoutRecursion: + (nullable AllNullableTypesWithoutRecursion *)arg_everything + completion:(void (^)(AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + @"echoAllNullableTypesWithoutRecursion"; + FlutterBasicMessageChannel *channel = + [FlutterBasicMessageChannel messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FlutterIntegrationCoreApiGetCodec()]; + [channel sendMessage:@[ arg_everything ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + AllNullableTypesWithoutRecursion *output = + reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, createConnectionError(channelName)); + } + }]; +} +- (void) + sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)arg_aNullableBool + anInt:(nullable NSNumber *)arg_aNullableInt + aString:(nullable NSString *)arg_aNullableString + completion: + (void (^)(AllNullableTypesWithoutRecursion *_Nullable, + FlutterError *_Nullable))completion { + NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + @"sendMultipleNullableTypesWithoutRecursion"; + FlutterBasicMessageChannel *channel = + [FlutterBasicMessageChannel messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FlutterIntegrationCoreApiGetCodec()]; + [channel sendMessage:@[ + arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], + arg_aNullableString ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + AllNullableTypesWithoutRecursion *output = + reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, createConnectionError(channelName)); + } + }]; +} - (void)echoBool:(BOOL)arg_aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart index 4ebee6b740a6..876c3b149d7e 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart @@ -106,6 +106,62 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { allNullableTypesTwo.aNullableObject); expect( allNullableTypesOne.aNullableEnum, allNullableTypesTwo.aNullableEnum); + compareAllNullableTypes(allNullableTypesOne.allNullableTypes, + allNullableTypesTwo.allNullableTypes); + } + + void compareAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? allNullableTypesOne, + AllNullableTypesWithoutRecursion? allNullableTypesTwo) { + expect(allNullableTypesOne == null, allNullableTypesTwo == null); + if (allNullableTypesOne == null || allNullableTypesTwo == null) { + return; + } + expect( + allNullableTypesOne.aNullableBool, allNullableTypesTwo.aNullableBool); + expect(allNullableTypesOne.aNullableInt, allNullableTypesTwo.aNullableInt); + expect( + allNullableTypesOne.aNullableInt64, allNullableTypesTwo.aNullableInt64); + expect(allNullableTypesOne.aNullableDouble, + allNullableTypesTwo.aNullableDouble); + expect(allNullableTypesOne.aNullableString, + allNullableTypesTwo.aNullableString); + expect(allNullableTypesOne.aNullableByteArray, + allNullableTypesTwo.aNullableByteArray); + expect(allNullableTypesOne.aNullable4ByteArray, + allNullableTypesTwo.aNullable4ByteArray); + expect(allNullableTypesOne.aNullable8ByteArray, + allNullableTypesTwo.aNullable8ByteArray); + expect(allNullableTypesOne.aNullableFloatArray, + allNullableTypesTwo.aNullableFloatArray); + expect( + listEquals(allNullableTypesOne.aNullableList, + allNullableTypesTwo.aNullableList), + true); + expect( + mapEquals( + allNullableTypesOne.aNullableMap, allNullableTypesTwo.aNullableMap), + true); + expect(allNullableTypesOne.nullableNestedList?.length, + allNullableTypesTwo.nullableNestedList?.length); + // TODO(stuartmorgan): Enable this once the Dart types are fixed; see + // https://github.com/flutter/flutter/issues/116117 + //for (int i = 0; i < allNullableTypesOne.nullableNestedList!.length; i++) { + // expect(listEquals(allNullableTypesOne.nullableNestedList![i], allNullableTypesTwo.nullableNestedList![i]), + // true); + //} + expect( + mapEquals(allNullableTypesOne.nullableMapWithAnnotations, + allNullableTypesTwo.nullableMapWithAnnotations), + true); + expect( + mapEquals(allNullableTypesOne.nullableMapWithObject, + allNullableTypesTwo.nullableMapWithObject), + true); + expect(allNullableTypesOne.aNullableObject, + allNullableTypesTwo.aNullableObject); + expect( + allNullableTypesOne.aNullableEnum, allNullableTypesTwo.aNullableEnum); } void compareAllClassesWrapper( @@ -117,6 +173,10 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { compareAllNullableTypes( wrapperOne.allNullableTypes, wrapperTwo.allNullableTypes); + compareAllNullableTypesWithoutRecursion( + wrapperOne.allNullableTypesWithoutRecursion, + wrapperTwo.allNullableTypesWithoutRecursion, + ); compareAllTypes(wrapperOne.allTypes, wrapperTwo.allTypes); } @@ -170,6 +230,65 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { aNullableObject: 0, ); + final AllNullableTypes recursiveAllNullableTypes = AllNullableTypes( + aNullableBool: true, + aNullableInt: _regularInt, + aNullableInt64: _biggerThanBigInt, + aNullableDouble: _doublePi, + aNullableString: 'Hello host!', + aNullableByteArray: Uint8List.fromList([1, 2, 3]), + aNullable4ByteArray: Int32List.fromList([4, 5, 6]), + aNullable8ByteArray: Int64List.fromList([7, 8, 9]), + aNullableFloatArray: Float64List.fromList([2.71828, _doublePi]), + aNullableList: ['Thing 1', 2, true, 3.14, null], + aNullableMap: { + 'a': 1, + 'b': 2.0, + 'c': 'three', + 'd': false, + 'e': null + }, + nullableNestedList: >[ + [true, false], + [false, true] + ], + nullableMapWithAnnotations: {}, + nullableMapWithObject: {}, + aNullableEnum: AnEnum.fourHundredTwentyTwo, + aNullableObject: 0, + allNullableTypes: genericAllNullableTypes, + ); + + final AllNullableTypesWithoutRecursion + genericAllNullableTypesWithoutRecursion = + AllNullableTypesWithoutRecursion( + aNullableBool: true, + aNullableInt: _regularInt, + aNullableInt64: _biggerThanBigInt, + aNullableDouble: _doublePi, + aNullableString: 'Hello host!', + aNullableByteArray: Uint8List.fromList([1, 2, 3]), + aNullable4ByteArray: Int32List.fromList([4, 5, 6]), + aNullable8ByteArray: Int64List.fromList([7, 8, 9]), + aNullableFloatArray: Float64List.fromList([2.71828, _doublePi]), + aNullableList: ['Thing 1', 2, true, 3.14, null], + aNullableMap: { + 'a': 1, + 'b': 2.0, + 'c': 'three', + 'd': false, + 'e': null + }, + nullableNestedList: >[ + [true, false], + [false, true] + ], + nullableMapWithAnnotations: {}, + nullableMapWithObject: {}, + aNullableEnum: AnEnum.fourHundredTwentyTwo, + aNullableObject: 0, + ); + group('Host sync API tests', () { testWidgets('basic void->void call works', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -190,9 +309,9 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllNullableTypes? echoObject = - await api.echoAllNullableTypes(genericAllNullableTypes); + await api.echoAllNullableTypes(recursiveAllNullableTypes); - compareAllNullableTypes(echoObject, genericAllNullableTypes); + compareAllNullableTypes(echoObject, recursiveAllNullableTypes); }); testWidgets('all null datatypes serialize and deserialize correctly', @@ -232,6 +351,67 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { compareAllNullableTypes(nullableListTypes, echoNullFilledClass); }); + testWidgets( + 'all nullable datatypes without recursion serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion? echoObject = + await api.echoAllNullableTypesWithoutRecursion( + genericAllNullableTypesWithoutRecursion); + + compareAllNullableTypesWithoutRecursion( + echoObject, genericAllNullableTypesWithoutRecursion); + }); + + testWidgets( + 'all null datatypes without recursion serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion allTypesNull = + AllNullableTypesWithoutRecursion(); + + final AllNullableTypesWithoutRecursion? echoNullFilledClass = + await api.echoAllNullableTypesWithoutRecursion(allTypesNull); + compareAllNullableTypesWithoutRecursion( + allTypesNull, echoNullFilledClass); + }); + + testWidgets( + 'Classes without recursion with list of null serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion nullableListTypes = + AllNullableTypesWithoutRecursion( + aNullableList: ['String', null]); + + final AllNullableTypesWithoutRecursion? echoNullFilledClass = + await api.echoAllNullableTypesWithoutRecursion(nullableListTypes); + + compareAllNullableTypesWithoutRecursion( + nullableListTypes, echoNullFilledClass); + }); + + testWidgets( + 'Classes without recursion with map of null serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion nullableListTypes = + AllNullableTypesWithoutRecursion(aNullableMap: { + 'String': 'string', + 'null': null + }); + + final AllNullableTypesWithoutRecursion? echoNullFilledClass = + await api.echoAllNullableTypesWithoutRecursion(nullableListTypes); + + compareAllNullableTypesWithoutRecursion( + nullableListTypes, echoNullFilledClass); + }); + testWidgets('errors are returned correctly', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -266,7 +446,10 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllClassesWrapper sentObject = AllClassesWrapper( - allNullableTypes: genericAllNullableTypes, allTypes: genericAllTypes); + allNullableTypes: recursiveAllNullableTypes, + allNullableTypesWithoutRecursion: + genericAllNullableTypesWithoutRecursion, + allTypes: genericAllTypes); final String? receivedString = await api.extractNestedNullableString(sentObject); @@ -288,7 +471,10 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllClassesWrapper sentWrapper = AllClassesWrapper( - allNullableTypes: AllNullableTypes(), allTypes: genericAllTypes); + allNullableTypes: AllNullableTypes(), + allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion(), + allTypes: genericAllTypes, + ); final AllClassesWrapper receivedClassWrapper = await api.echoClassWrapper(sentWrapper); @@ -299,8 +485,10 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); - final AllClassesWrapper sentWrapper = - AllClassesWrapper(allNullableTypes: AllNullableTypes()); + final AllClassesWrapper sentWrapper = AllClassesWrapper( + allNullableTypes: AllNullableTypes(), + allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion(), + ); final AllClassesWrapper receivedClassWrapper = await api.echoClassWrapper(sentWrapper); @@ -334,6 +522,34 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { expect(echoNullFilledClass.aNullableString, null); }); + testWidgets( + 'Arguments of multiple types serialize and deserialize correctly (WithoutRecursion)', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + const String aNullableString = 'this is a String'; + const bool aNullableBool = false; + const int aNullableInt = _regularInt; + + final AllNullableTypesWithoutRecursion echoObject = + await api.sendMultipleNullableTypesWithoutRecursion( + aNullableBool, aNullableInt, aNullableString); + expect(echoObject.aNullableInt, aNullableInt); + expect(echoObject.aNullableBool, aNullableBool); + expect(echoObject.aNullableString, aNullableString); + }); + + testWidgets( + 'Arguments of multiple null types serialize and deserialize correctly (WithoutRecursion)', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion echoNullFilledClass = + await api.sendMultipleNullableTypesWithoutRecursion(null, null, null); + expect(echoNullFilledClass.aNullableInt, null); + expect(echoNullFilledClass.aNullableBool, null); + expect(echoNullFilledClass.aNullableString, null); + }); + testWidgets('Int serialize and deserialize correctly', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -775,10 +991,10 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); - final AllNullableTypes? echoObject = - await api.echoAsyncNullableAllNullableTypes(genericAllNullableTypes); + final AllNullableTypes? echoObject = await api + .echoAsyncNullableAllNullableTypes(recursiveAllNullableTypes); - compareAllNullableTypes(echoObject, genericAllNullableTypes); + compareAllNullableTypes(echoObject, recursiveAllNullableTypes); }); testWidgets('all null datatypes async serialize and deserialize correctly', @@ -792,6 +1008,33 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { compareAllNullableTypes(echoNullFilledClass, allTypesNull); }); + testWidgets( + 'all nullable async datatypes without recursion serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion? echoObject = + await api.echoAsyncNullableAllNullableTypesWithoutRecursion( + genericAllNullableTypesWithoutRecursion); + + compareAllNullableTypesWithoutRecursion( + echoObject, genericAllNullableTypesWithoutRecursion); + }); + + testWidgets( + 'all null datatypes without recursion async serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion allTypesNull = + AllNullableTypesWithoutRecursion(); + + final AllNullableTypesWithoutRecursion? echoNullFilledClass = await api + .echoAsyncNullableAllNullableTypesWithoutRecursion(allTypesNull); + compareAllNullableTypesWithoutRecursion( + echoNullFilledClass, allTypesNull); + }); + testWidgets('Int async serialize and deserialize correctly', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -1187,6 +1430,35 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { expect(compositeObject.aNullableString, null); }); + testWidgets( + 'Arguments of multiple types serialize and deserialize correctly (WithoutRecursion)', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + const String aNullableString = 'this is a String'; + const bool aNullableBool = false; + const int aNullableInt = _regularInt; + + final AllNullableTypesWithoutRecursion compositeObject = + await api.callFlutterSendMultipleNullableTypesWithoutRecursion( + aNullableBool, aNullableInt, aNullableString); + expect(compositeObject.aNullableInt, aNullableInt); + expect(compositeObject.aNullableBool, aNullableBool); + expect(compositeObject.aNullableString, aNullableString); + }); + + testWidgets( + 'Arguments of multiple null types serialize and deserialize correctly (WithoutRecursion)', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypesWithoutRecursion compositeObject = + await api.callFlutterSendMultipleNullableTypesWithoutRecursion( + null, null, null); + expect(compositeObject.aNullableInt, null); + expect(compositeObject.aNullableBool, null); + expect(compositeObject.aNullableString, null); + }); + testWidgets('booleans serialize and deserialize correctly', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -1483,6 +1755,12 @@ class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { return everything; } + @override + AllNullableTypesWithoutRecursion? echoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything) { + return everything; + } + @override void noop() {} @@ -1505,6 +1783,15 @@ class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { aNullableString: aNullableString); } + @override + AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + bool? aNullableBool, int? aNullableInt, String? aNullableString) { + return AllNullableTypesWithoutRecursion( + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableString: aNullableString); + } + @override bool echoBool(bool aBool) => aBool; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index 98c1aea5c084..1b3bc1f5304e 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -139,6 +139,7 @@ class AllNullableTypes { this.aNullableEnum, this.aNullableString, this.aNullableObject, + this.allNullableTypes, }); bool? aNullableBool; @@ -173,6 +174,8 @@ class AllNullableTypes { Object? aNullableObject; + AllNullableTypes? allNullableTypes; + Object encode() { return [ aNullableBool, @@ -191,6 +194,7 @@ class AllNullableTypes { aNullableEnum?.index, aNullableString, aNullableObject, + allNullableTypes?.encode(), ]; } @@ -216,6 +220,111 @@ class AllNullableTypes { result[13] != null ? AnEnum.values[result[13]! as int] : null, aNullableString: result[14] as String?, aNullableObject: result[15], + allNullableTypes: result[16] != null + ? AllNullableTypes.decode(result[16]! as List) + : null, + ); + } +} + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +class AllNullableTypesWithoutRecursion { + AllNullableTypesWithoutRecursion({ + this.aNullableBool, + this.aNullableInt, + this.aNullableInt64, + this.aNullableDouble, + this.aNullableByteArray, + this.aNullable4ByteArray, + this.aNullable8ByteArray, + this.aNullableFloatArray, + this.aNullableList, + this.aNullableMap, + this.nullableNestedList, + this.nullableMapWithAnnotations, + this.nullableMapWithObject, + this.aNullableEnum, + this.aNullableString, + this.aNullableObject, + }); + + bool? aNullableBool; + + int? aNullableInt; + + int? aNullableInt64; + + double? aNullableDouble; + + Uint8List? aNullableByteArray; + + Int32List? aNullable4ByteArray; + + Int64List? aNullable8ByteArray; + + Float64List? aNullableFloatArray; + + List? aNullableList; + + Map? aNullableMap; + + List?>? nullableNestedList; + + Map? nullableMapWithAnnotations; + + Map? nullableMapWithObject; + + AnEnum? aNullableEnum; + + String? aNullableString; + + Object? aNullableObject; + + Object encode() { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableList, + aNullableMap, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum?.index, + aNullableString, + aNullableObject, + ]; + } + + static AllNullableTypesWithoutRecursion decode(Object result) { + result as List; + return AllNullableTypesWithoutRecursion( + aNullableBool: result[0] as bool?, + aNullableInt: result[1] as int?, + aNullableInt64: result[2] as int?, + aNullableDouble: result[3] as double?, + aNullableByteArray: result[4] as Uint8List?, + aNullable4ByteArray: result[5] as Int32List?, + aNullable8ByteArray: result[6] as Int64List?, + aNullableFloatArray: result[7] as Float64List?, + aNullableList: result[8] as List?, + aNullableMap: result[9] as Map?, + nullableNestedList: (result[10] as List?)?.cast?>(), + nullableMapWithAnnotations: + (result[11] as Map?)?.cast(), + nullableMapWithObject: + (result[12] as Map?)?.cast(), + aNullableEnum: + result[13] != null ? AnEnum.values[result[13]! as int] : null, + aNullableString: result[14] as String?, + aNullableObject: result[15], ); } } @@ -228,16 +337,20 @@ class AllNullableTypes { class AllClassesWrapper { AllClassesWrapper({ required this.allNullableTypes, + this.allNullableTypesWithoutRecursion, this.allTypes, }); AllNullableTypes allNullableTypes; + AllNullableTypesWithoutRecursion? allNullableTypesWithoutRecursion; + AllTypes? allTypes; Object encode() { return [ allNullableTypes.encode(), + allNullableTypesWithoutRecursion?.encode(), allTypes?.encode(), ]; } @@ -246,8 +359,11 @@ class AllClassesWrapper { result as List; return AllClassesWrapper( allNullableTypes: AllNullableTypes.decode(result[0]! as List), - allTypes: result[1] != null - ? AllTypes.decode(result[1]! as List) + allNullableTypesWithoutRecursion: result[1] != null + ? AllNullableTypesWithoutRecursion.decode(result[1]! as List) + : null, + allTypes: result[2] != null + ? AllTypes.decode(result[2]! as List) : null, ); } @@ -285,12 +401,15 @@ class _HostIntegrationCoreApiCodec extends StandardMessageCodec { } else if (value is AllNullableTypes) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is AllTypes) { + } else if (value is AllNullableTypesWithoutRecursion) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is TestMessage) { + } else if (value is AllTypes) { buffer.putUint8(131); writeValue(buffer, value.encode()); + } else if (value is TestMessage) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -304,8 +423,10 @@ class _HostIntegrationCoreApiCodec extends StandardMessageCodec { case 129: return AllNullableTypes.decode(readValue(buffer)!); case 130: - return AllTypes.decode(readValue(buffer)!); + return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!); case 131: + return AllTypes.decode(readValue(buffer)!); + case 132: return TestMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -874,6 +995,33 @@ class HostIntegrationCoreApi { } } + /// Returns the passed object, to test serialization and deserialization. + Future + echoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([everything]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as AllNullableTypesWithoutRecursion?); + } + } + /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. Future extractNestedNullableString(AllClassesWrapper wrapper) async { @@ -964,6 +1112,39 @@ class HostIntegrationCoreApi { } } + /// Returns passed in arguments of multiple types. + Future + sendMultipleNullableTypesWithoutRecursion(bool? aNullableBool, + int? aNullableInt, String? aNullableString) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([aNullableBool, aNullableInt, aNullableString]) + as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as AllNullableTypesWithoutRecursion?)!; + } + } + /// Returns passed in int. Future echoNullableInt(int? aNullableInt) async { const String __pigeon_channelName = @@ -1671,6 +1852,33 @@ class HostIntegrationCoreApi { } } + /// Returns the passed object, to test serialization and deserialization. + Future + echoAsyncNullableAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableAllNullableTypesWithoutRecursion'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([everything]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as AllNullableTypesWithoutRecursion?); + } + } + /// Returns passed in int asynchronously. Future echoAsyncNullableInt(int? anInt) async { const String __pigeon_channelName = @@ -2057,6 +2265,64 @@ class HostIntegrationCoreApi { } } + Future + callFlutterEchoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoAllNullableTypesWithoutRecursion'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([everything]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as AllNullableTypesWithoutRecursion?); + } + } + + Future + callFlutterSendMultipleNullableTypesWithoutRecursion(bool? aNullableBool, + int? aNullableInt, String? aNullableString) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterSendMultipleNullableTypesWithoutRecursion'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([aNullableBool, aNullableInt, aNullableString]) + as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as AllNullableTypesWithoutRecursion?)!; + } + } + Future callFlutterEchoBool(bool aBool) async { const String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoBool'; @@ -2499,12 +2765,15 @@ class _FlutterIntegrationCoreApiCodec extends StandardMessageCodec { } else if (value is AllNullableTypes) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is AllTypes) { + } else if (value is AllNullableTypesWithoutRecursion) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is TestMessage) { + } else if (value is AllTypes) { buffer.putUint8(131); writeValue(buffer, value.encode()); + } else if (value is TestMessage) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -2518,8 +2787,10 @@ class _FlutterIntegrationCoreApiCodec extends StandardMessageCodec { case 129: return AllNullableTypes.decode(readValue(buffer)!); case 130: - return AllTypes.decode(readValue(buffer)!); + return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!); case 131: + return AllTypes.decode(readValue(buffer)!); + case 132: return TestMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -2555,6 +2826,16 @@ abstract class FlutterIntegrationCoreApi { AllNullableTypes sendMultipleNullableTypes( bool? aNullableBool, int? aNullableInt, String? aNullableString); + /// Returns the passed object, to test serialization and deserialization. + AllNullableTypesWithoutRecursion? echoAllNullableTypesWithoutRecursion( + AllNullableTypesWithoutRecursion? everything); + + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( + bool? aNullableBool, int? aNullableInt, String? aNullableString); + /// Returns the passed boolean, to test serialization and deserialization. bool echoBool(bool aBool); @@ -2763,6 +3044,64 @@ abstract class FlutterIntegrationCoreApi { }); } } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion was null.'); + final List args = (message as List?)!; + final AllNullableTypesWithoutRecursion? arg_everything = + (args[0] as AllNullableTypesWithoutRecursion?); + try { + final AllNullableTypesWithoutRecursion? output = + api.echoAllNullableTypesWithoutRecursion(arg_everything); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion was null.'); + final List args = (message as List?)!; + final bool? arg_aNullableBool = (args[0] as bool?); + final int? arg_aNullableInt = (args[1] as int?); + final String? arg_aNullableString = (args[2] as String?); + try { + final AllNullableTypesWithoutRecursion output = + api.sendMultipleNullableTypesWithoutRecursion( + arg_aNullableBool, arg_aNullableInt, arg_aNullableString); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final BasicMessageChannel __pigeon_channel = BasicMessageChannel< Object?>( diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart new file mode 100644 index 000000000000..762f65d5aad0 --- /dev/null +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart @@ -0,0 +1,5023 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Autogenerated from Pigeon, 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, 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, immutable, protected; +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]; +} + +/// An immutable object that serves as the base class for all ProxyApis 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 PigeonProxyApiBaseClass { + /// Construct a [PigeonProxyApiBaseClass]. + PigeonProxyApiBaseClass({ + 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. + @protected + 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 + PigeonProxyApiBaseClass 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 ProxyApis. + /// + /// 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() { + WidgetsFlutterBinding.ensureInitialized(); + final _PigeonInstanceManagerApi api = _PigeonInstanceManagerApi(); + // Clears the native `PigeonInstanceManager` on the initial use of the Dart one. + api.clear(); + final PigeonInstanceManager instanceManager = PigeonInstanceManager( + onWeakReferenceRemoved: (int identifier) { + api.removeStrongReference(identifier); + }, + ); + _PigeonInstanceManagerApi.setUpMessageHandlers( + instanceManager: instanceManager); + ProxyApiTestClass.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ProxyApiSuperClass.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ProxyApiInterface.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(PigeonProxyApiBaseClass instance) { + final int identifier = _nextUniqueIdentifier(); + _addInstanceWithIdentifier(instance, identifier); + 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(PigeonProxyApiBaseClass 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. + /// + /// This does not remove the weak referenced instance associated with + /// [identifier]. This can be done with [removeWeakReference]. + T? remove(int identifier) { + 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 PigeonProxyApiBaseClass? weakInstance = + _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final PigeonProxyApiBaseClass? strongInstance = + _strongInstances[identifier]; + if (strongInstance != null) { + final PigeonProxyApiBaseClass 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(PigeonProxyApiBaseClass 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. + /// + /// Returns unique identifier of the [instance] added. + void addHostCreatedInstance( + PigeonProxyApiBaseClass instance, int identifier) { + _addInstanceWithIdentifier(instance, identifier); + } + + void _addInstanceWithIdentifier( + PigeonProxyApiBaseClass instance, int identifier) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + + _identifiers[instance] = identifier; + _weakInstances[identifier] = + WeakReference(instance); + _finalizer.attach(instance, identifier, detach: instance); + + final PigeonProxyApiBaseClass copy = instance.pigeon_copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + } + + /// 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 _PigeonInstanceManagerApi { + /// Constructor for [_PigeonInstanceManagerApi]. + _PigeonInstanceManagerApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec pigeonChannelCodec = + StandardMessageCodec(); + + static void setUpMessageHandlers({ + BinaryMessenger? binaryMessenger, + PigeonInstanceManager? instanceManager, + }) { + const String channelName = + r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for $channelName was null.', + ); + final int? identifier = message as int?; + assert( + identifier != null, + r'Argument for $channelName, expected non-null int.', + ); + (instanceManager ?? PigeonInstanceManager.instance).remove(identifier!); + return; + }); + } + + Future removeStrongReference(int identifier) async { + const String channelName = + r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + pigeonChannelCodec, + binaryMessenger: _binaryMessenger, + ); + final List? replyList = + await channel.send(identifier) as List?; + if (replyList == null) { + throw _createConnectionError(channelName); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Clear the native `PigeonInstanceManager`. + /// + /// This is typically called after a hot restart. + Future clear() async { + const String channelName = + r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.clear'; + final BasicMessageChannel channel = BasicMessageChannel( + channelName, + pigeonChannelCodec, + binaryMessenger: _binaryMessenger, + ); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw _createConnectionError(channelName); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +class _PigeonProxyApiBaseCodec extends StandardMessageCodec { + const _PigeonProxyApiBaseCodec(this.instanceManager); + final PigeonInstanceManager instanceManager; + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PigeonProxyApiBaseClass) { + 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); + } + } +} + +enum ProxyApiTestEnum { + one, + two, + three, +} + +/// The core ProxyApi test class that each supported host language must +/// implement in platform_tests integration tests. +class ProxyApiTestClass extends ProxyApiSuperClass + implements ProxyApiInterface { + ProxyApiTestClass({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.aBool, + required this.anInt, + required this.aDouble, + required this.aString, + required this.aUint8List, + required this.aList, + required this.aMap, + required this.anEnum, + required this.aProxyApi, + this.aNullableBool, + this.aNullableInt, + this.aNullableDouble, + this.aNullableString, + this.aNullableUint8List, + this.aNullableList, + this.aNullableMap, + this.aNullableEnum, + this.aNullableProxyApi, + this.anInterfaceMethod, + this.flutterNoop, + this.flutterThrowError, + this.flutterThrowErrorFromVoid, + this.flutterEchoBool, + this.flutterEchoInt, + this.flutterEchoDouble, + this.flutterEchoString, + this.flutterEchoUint8List, + this.flutterEchoList, + this.flutterEchoProxyApiList, + this.flutterEchoMap, + this.flutterEchoProxyApiMap, + this.flutterEchoEnum, + this.flutterEchoProxyApi, + this.flutterEchoNullableBool, + this.flutterEchoNullableInt, + this.flutterEchoNullableDouble, + this.flutterEchoNullableString, + this.flutterEchoNullableUint8List, + this.flutterEchoNullableList, + this.flutterEchoNullableMap, + this.flutterEchoNullableEnum, + this.flutterEchoNullableProxyApi, + this.flutterNoopAsync, + this.flutterEchoAsyncString, + required bool boolParam, + required int intParam, + required double doubleParam, + required String stringParam, + required Uint8List aUint8ListParam, + required List listParam, + required Map mapParam, + required ProxyApiTestEnum enumParam, + required ProxyApiSuperClass proxyApiParam, + bool? nullableBoolParam, + int? nullableIntParam, + double? nullableDoubleParam, + String? nullableStringParam, + Uint8List? nullableUint8ListParam, + List? nullableListParam, + Map? nullableMapParam, + ProxyApiTestEnum? nullableEnumParam, + ProxyApiSuperClass? nullableProxyApiParam, + }) : super.pigeon_detached() { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([ + __pigeon_instanceIdentifier, + aBool, + anInt, + aDouble, + aString, + aUint8List, + aList, + aMap, + anEnum.index, + aProxyApi, + aNullableBool, + aNullableInt, + aNullableDouble, + aNullableString, + aNullableUint8List, + aNullableList, + aNullableMap, + aNullableEnum?.index, + aNullableProxyApi, + boolParam, + intParam, + doubleParam, + stringParam, + aUint8ListParam, + listParam, + mapParam, + enumParam.index, + proxyApiParam, + nullableBoolParam, + nullableIntParam, + nullableDoubleParam, + nullableStringParam, + nullableUint8ListParam, + nullableListParam, + nullableMapParam, + nullableEnumParam?.index, + nullableProxyApiParam + ]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [ProxyApiTestClass] 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 + ProxyApiTestClass.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.aBool, + required this.anInt, + required this.aDouble, + required this.aString, + required this.aUint8List, + required this.aList, + required this.aMap, + required this.anEnum, + required this.aProxyApi, + this.aNullableBool, + this.aNullableInt, + this.aNullableDouble, + this.aNullableString, + this.aNullableUint8List, + this.aNullableList, + this.aNullableMap, + this.aNullableEnum, + this.aNullableProxyApi, + this.anInterfaceMethod, + this.flutterNoop, + this.flutterThrowError, + this.flutterThrowErrorFromVoid, + this.flutterEchoBool, + this.flutterEchoInt, + this.flutterEchoDouble, + this.flutterEchoString, + this.flutterEchoUint8List, + this.flutterEchoList, + this.flutterEchoProxyApiList, + this.flutterEchoMap, + this.flutterEchoProxyApiMap, + this.flutterEchoEnum, + this.flutterEchoProxyApi, + this.flutterEchoNullableBool, + this.flutterEchoNullableInt, + this.flutterEchoNullableDouble, + this.flutterEchoNullableString, + this.flutterEchoNullableUint8List, + this.flutterEchoNullableList, + this.flutterEchoNullableMap, + this.flutterEchoNullableEnum, + this.flutterEchoNullableProxyApi, + this.flutterNoopAsync, + this.flutterEchoAsyncString, + }) : super.pigeon_detached(); + + late final _PigeonProxyApiBaseCodec __pigeon_codecProxyApiTestClass = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + final bool aBool; + + final int anInt; + + final double aDouble; + + final String aString; + + final Uint8List aUint8List; + + final List aList; + + final Map aMap; + + final ProxyApiTestEnum anEnum; + + final ProxyApiSuperClass aProxyApi; + + final bool? aNullableBool; + + final int? aNullableInt; + + final double? aNullableDouble; + + final String? aNullableString; + + final Uint8List? aNullableUint8List; + + final List? aNullableList; + + final Map? aNullableMap; + + final ProxyApiTestEnum? aNullableEnum; + + final ProxyApiSuperClass? aNullableProxyApi; + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic calling. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterNoop: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(ProxyApiTestClass pigeon_instance)? flutterNoop; + + /// Responds with an error from an async function returning a value. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterThrowError: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Object? Function(ProxyApiTestClass pigeon_instance)? flutterThrowError; + + /// Responds with an error from an async void function. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterThrowErrorFromVoid: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(ProxyApiTestClass pigeon_instance)? + flutterThrowErrorFromVoid; + + /// Returns the passed boolean, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoBool: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final bool Function( + ProxyApiTestClass pigeon_instance, + bool aBool, + )? flutterEchoBool; + + /// Returns the passed int, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoInt: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final int Function( + ProxyApiTestClass pigeon_instance, + int anInt, + )? flutterEchoInt; + + /// Returns the passed double, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoDouble: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final double Function( + ProxyApiTestClass pigeon_instance, + double aDouble, + )? flutterEchoDouble; + + /// Returns the passed string, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoString: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final String Function( + ProxyApiTestClass pigeon_instance, + String aString, + )? flutterEchoString; + + /// Returns the passed byte list, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoUint8List: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Uint8List Function( + ProxyApiTestClass pigeon_instance, + Uint8List aList, + )? flutterEchoUint8List; + + /// Returns the passed list, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoList: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final List Function( + ProxyApiTestClass pigeon_instance, + List aList, + )? flutterEchoList; + + /// Returns the passed list with ProxyApis, to test serialization and + /// deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoProxyApiList: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final List Function( + ProxyApiTestClass pigeon_instance, + List aList, + )? flutterEchoProxyApiList; + + /// Returns the passed map, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoMap: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Map Function( + ProxyApiTestClass pigeon_instance, + Map aMap, + )? flutterEchoMap; + + /// Returns the passed map with ProxyApis, to test serialization and + /// deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoProxyApiMap: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Map Function( + ProxyApiTestClass pigeon_instance, + Map aMap, + )? flutterEchoProxyApiMap; + + /// Returns the passed enum to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoEnum: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final ProxyApiTestEnum Function( + ProxyApiTestClass pigeon_instance, + ProxyApiTestEnum anEnum, + )? flutterEchoEnum; + + /// Returns the passed ProxyApi to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoProxyApi: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final ProxyApiSuperClass Function( + ProxyApiTestClass pigeon_instance, + ProxyApiSuperClass aProxyApi, + )? flutterEchoProxyApi; + + /// Returns the passed boolean, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableBool: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final bool? Function( + ProxyApiTestClass pigeon_instance, + bool? aBool, + )? flutterEchoNullableBool; + + /// Returns the passed int, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableInt: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final int? Function( + ProxyApiTestClass pigeon_instance, + int? anInt, + )? flutterEchoNullableInt; + + /// Returns the passed double, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableDouble: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final double? Function( + ProxyApiTestClass pigeon_instance, + double? aDouble, + )? flutterEchoNullableDouble; + + /// Returns the passed string, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableString: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final String? Function( + ProxyApiTestClass pigeon_instance, + String? aString, + )? flutterEchoNullableString; + + /// Returns the passed byte list, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableUint8List: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Uint8List? Function( + ProxyApiTestClass pigeon_instance, + Uint8List? aList, + )? flutterEchoNullableUint8List; + + /// Returns the passed list, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableList: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final List? Function( + ProxyApiTestClass pigeon_instance, + List? aList, + )? flutterEchoNullableList; + + /// Returns the passed map, to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableMap: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Map? Function( + ProxyApiTestClass pigeon_instance, + Map? aMap, + )? flutterEchoNullableMap; + + /// Returns the passed enum to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableEnum: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final ProxyApiTestEnum? Function( + ProxyApiTestClass pigeon_instance, + ProxyApiTestEnum? anEnum, + )? flutterEchoNullableEnum; + + /// Returns the passed ProxyApi to test serialization and deserialization. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoNullableProxyApi: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final ProxyApiSuperClass? Function( + ProxyApiTestClass pigeon_instance, + ProxyApiSuperClass? aProxyApi, + )? flutterEchoNullableProxyApi; + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic asynchronous calling. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterNoopAsync: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future Function(ProxyApiTestClass pigeon_instance)? + flutterNoopAsync; + + /// Returns the passed in generic Object asynchronously. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiTestClass instance = ProxyApiTestClass( + /// flutterEchoAsyncString: (ProxyApiTestClass pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future Function( + ProxyApiTestClass pigeon_instance, + String aString, + )? flutterEchoAsyncString; + + @override + final void Function(ProxyApiInterface pigeon_instance)? anInterfaceMethod; + + late final ProxyApiSuperClass attachedField = __pigeon_attachedField(); + + static final ProxyApiSuperClass staticAttachedField = + __pigeon_staticAttachedField(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ProxyApiTestClass Function( + bool aBool, + int anInt, + double aDouble, + String aString, + Uint8List aUint8List, + List aList, + Map aMap, + ProxyApiTestEnum anEnum, + ProxyApiSuperClass aProxyApi, + bool? aNullableBool, + int? aNullableInt, + double? aNullableDouble, + String? aNullableString, + Uint8List? aNullableUint8List, + List? aNullableList, + Map? aNullableMap, + ProxyApiTestEnum? aNullableEnum, + ProxyApiSuperClass? aNullableProxyApi, + )? pigeon_newInstance, + void Function(ProxyApiTestClass pigeon_instance)? flutterNoop, + Object? Function(ProxyApiTestClass pigeon_instance)? flutterThrowError, + void Function(ProxyApiTestClass pigeon_instance)? flutterThrowErrorFromVoid, + bool Function( + ProxyApiTestClass pigeon_instance, + bool aBool, + )? flutterEchoBool, + int Function( + ProxyApiTestClass pigeon_instance, + int anInt, + )? flutterEchoInt, + double Function( + ProxyApiTestClass pigeon_instance, + double aDouble, + )? flutterEchoDouble, + String Function( + ProxyApiTestClass pigeon_instance, + String aString, + )? flutterEchoString, + Uint8List Function( + ProxyApiTestClass pigeon_instance, + Uint8List aList, + )? flutterEchoUint8List, + List Function( + ProxyApiTestClass pigeon_instance, + List aList, + )? flutterEchoList, + List Function( + ProxyApiTestClass pigeon_instance, + List aList, + )? flutterEchoProxyApiList, + Map Function( + ProxyApiTestClass pigeon_instance, + Map aMap, + )? flutterEchoMap, + Map Function( + ProxyApiTestClass pigeon_instance, + Map aMap, + )? flutterEchoProxyApiMap, + ProxyApiTestEnum Function( + ProxyApiTestClass pigeon_instance, + ProxyApiTestEnum anEnum, + )? flutterEchoEnum, + ProxyApiSuperClass Function( + ProxyApiTestClass pigeon_instance, + ProxyApiSuperClass aProxyApi, + )? flutterEchoProxyApi, + bool? Function( + ProxyApiTestClass pigeon_instance, + bool? aBool, + )? flutterEchoNullableBool, + int? Function( + ProxyApiTestClass pigeon_instance, + int? anInt, + )? flutterEchoNullableInt, + double? Function( + ProxyApiTestClass pigeon_instance, + double? aDouble, + )? flutterEchoNullableDouble, + String? Function( + ProxyApiTestClass pigeon_instance, + String? aString, + )? flutterEchoNullableString, + Uint8List? Function( + ProxyApiTestClass pigeon_instance, + Uint8List? aList, + )? flutterEchoNullableUint8List, + List? Function( + ProxyApiTestClass pigeon_instance, + List? aList, + )? flutterEchoNullableList, + Map? Function( + ProxyApiTestClass pigeon_instance, + Map? aMap, + )? flutterEchoNullableMap, + ProxyApiTestEnum? Function( + ProxyApiTestClass pigeon_instance, + ProxyApiTestEnum? anEnum, + )? flutterEchoNullableEnum, + ProxyApiSuperClass? Function( + ProxyApiTestClass pigeon_instance, + ProxyApiSuperClass? aProxyApi, + )? flutterEchoNullableProxyApi, + Future Function(ProxyApiTestClass pigeon_instance)? flutterNoopAsync, + Future Function( + ProxyApiTestClass pigeon_instance, + String aString, + )? flutterEchoAsyncString, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.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.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null int.'); + final bool? arg_aBool = (args[1] as bool?); + assert(arg_aBool != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null bool.'); + final int? arg_anInt = (args[2] as int?); + assert(arg_anInt != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null int.'); + final double? arg_aDouble = (args[3] as double?); + assert(arg_aDouble != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null double.'); + final String? arg_aString = (args[4] as String?); + assert(arg_aString != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null String.'); + final Uint8List? arg_aUint8List = (args[5] as Uint8List?); + assert(arg_aUint8List != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null Uint8List.'); + final List? arg_aList = + (args[6] as List?)?.cast(); + assert(arg_aList != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null List.'); + final Map? arg_aMap = + (args[7] as Map?)?.cast(); + assert(arg_aMap != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null Map.'); + final ProxyApiTestEnum? arg_anEnum = + args[8] == null ? null : ProxyApiTestEnum.values[args[8]! as int]; + assert(arg_anEnum != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null ProxyApiTestEnum.'); + final ProxyApiSuperClass? arg_aProxyApi = + (args[9] as ProxyApiSuperClass?); + assert(arg_aProxyApi != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null ProxyApiSuperClass.'); + final bool? arg_aNullableBool = (args[10] as bool?); + final int? arg_aNullableInt = (args[11] as int?); + final double? arg_aNullableDouble = (args[12] as double?); + final String? arg_aNullableString = (args[13] as String?); + final Uint8List? arg_aNullableUint8List = (args[14] as Uint8List?); + final List? arg_aNullableList = + (args[15] as List?)?.cast(); + final Map? arg_aNullableMap = + (args[16] as Map?)?.cast(); + final ProxyApiTestEnum? arg_aNullableEnum = args[17] == null + ? null + : ProxyApiTestEnum.values[args[17]! as int]; + final ProxyApiSuperClass? arg_aNullableProxyApi = + (args[18] as ProxyApiSuperClass?); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call( + arg_aBool!, + arg_anInt!, + arg_aDouble!, + arg_aString!, + arg_aUint8List!, + arg_aList!, + arg_aMap!, + arg_anEnum!, + arg_aProxyApi!, + arg_aNullableBool, + arg_aNullableInt, + arg_aNullableDouble, + arg_aNullableString, + arg_aNullableUint8List, + arg_aNullableList, + arg_aNullableMap, + arg_aNullableEnum, + arg_aNullableProxyApi) ?? + ProxyApiTestClass.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + aBool: arg_aBool!, + anInt: arg_anInt!, + aDouble: arg_aDouble!, + aString: arg_aString!, + aUint8List: arg_aUint8List!, + aList: arg_aList!, + aMap: arg_aMap!, + anEnum: arg_anEnum!, + aProxyApi: arg_aProxyApi!, + aNullableBool: arg_aNullableBool, + aNullableInt: arg_aNullableInt, + aNullableDouble: arg_aNullableDouble, + aNullableString: arg_aNullableString, + aNullableUint8List: arg_aNullableUint8List, + aNullableList: arg_aNullableList, + aNullableMap: arg_aNullableMap, + aNullableEnum: arg_aNullableEnum, + aNullableProxyApi: arg_aNullableProxyApi, + ), + 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())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoop', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoop was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoop was null, expected non-null ProxyApiTestClass.'); + try { + (flutterNoop ?? arg_pigeon_instance!.flutterNoop) + ?.call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowError was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowError was null, expected non-null ProxyApiTestClass.'); + try { + final Object? output = + (flutterThrowError ?? arg_pigeon_instance!.flutterThrowError) + ?.call(arg_pigeon_instance!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowErrorFromVoid', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowErrorFromVoid was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterThrowErrorFromVoid was null, expected non-null ProxyApiTestClass.'); + try { + (flutterThrowErrorFromVoid ?? + arg_pigeon_instance!.flutterThrowErrorFromVoid) + ?.call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoBool', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoBool was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoBool was null, expected non-null ProxyApiTestClass.'); + final bool? arg_aBool = (args[1] as bool?); + assert(arg_aBool != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoBool was null, expected non-null bool.'); + try { + final bool? output = + (flutterEchoBool ?? arg_pigeon_instance!.flutterEchoBool) + ?.call(arg_pigeon_instance!, arg_aBool!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoInt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoInt was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoInt was null, expected non-null ProxyApiTestClass.'); + final int? arg_anInt = (args[1] as int?); + assert(arg_anInt != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoInt was null, expected non-null int.'); + try { + final int? output = + (flutterEchoInt ?? arg_pigeon_instance!.flutterEchoInt) + ?.call(arg_pigeon_instance!, arg_anInt!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoDouble', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoDouble was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoDouble was null, expected non-null ProxyApiTestClass.'); + final double? arg_aDouble = (args[1] as double?); + assert(arg_aDouble != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoDouble was null, expected non-null double.'); + try { + final double? output = + (flutterEchoDouble ?? arg_pigeon_instance!.flutterEchoDouble) + ?.call(arg_pigeon_instance!, arg_aDouble!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoString', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoString was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoString was null, expected non-null ProxyApiTestClass.'); + final String? arg_aString = (args[1] as String?); + assert(arg_aString != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoString was null, expected non-null String.'); + try { + final String? output = + (flutterEchoString ?? arg_pigeon_instance!.flutterEchoString) + ?.call(arg_pigeon_instance!, arg_aString!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoUint8List', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoUint8List was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoUint8List was null, expected non-null ProxyApiTestClass.'); + final Uint8List? arg_aList = (args[1] as Uint8List?); + assert(arg_aList != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoUint8List was null, expected non-null Uint8List.'); + try { + final Uint8List? output = (flutterEchoUint8List ?? + arg_pigeon_instance!.flutterEchoUint8List) + ?.call(arg_pigeon_instance!, arg_aList!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoList', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoList was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoList was null, expected non-null ProxyApiTestClass.'); + final List? arg_aList = + (args[1] as List?)?.cast(); + assert(arg_aList != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoList was null, expected non-null List.'); + try { + final List? output = + (flutterEchoList ?? arg_pigeon_instance!.flutterEchoList) + ?.call(arg_pigeon_instance!, arg_aList!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiList', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiList was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiList was null, expected non-null ProxyApiTestClass.'); + final List? arg_aList = + (args[1] as List?)?.cast(); + assert(arg_aList != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiList was null, expected non-null List.'); + try { + final List? output = (flutterEchoProxyApiList ?? + arg_pigeon_instance!.flutterEchoProxyApiList) + ?.call(arg_pigeon_instance!, arg_aList!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoMap', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoMap was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoMap was null, expected non-null ProxyApiTestClass.'); + final Map? arg_aMap = + (args[1] as Map?)?.cast(); + assert(arg_aMap != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoMap was null, expected non-null Map.'); + try { + final Map? output = + (flutterEchoMap ?? arg_pigeon_instance!.flutterEchoMap) + ?.call(arg_pigeon_instance!, arg_aMap!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiMap', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiMap was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiMap was null, expected non-null ProxyApiTestClass.'); + final Map? arg_aMap = + (args[1] as Map?) + ?.cast(); + assert(arg_aMap != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApiMap was null, expected non-null Map.'); + try { + final Map? output = + (flutterEchoProxyApiMap ?? + arg_pigeon_instance!.flutterEchoProxyApiMap) + ?.call(arg_pigeon_instance!, arg_aMap!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum was null, expected non-null ProxyApiTestClass.'); + final ProxyApiTestEnum? arg_anEnum = + args[1] == null ? null : ProxyApiTestEnum.values[args[1]! as int]; + assert(arg_anEnum != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum was null, expected non-null ProxyApiTestEnum.'); + try { + final ProxyApiTestEnum? output = + (flutterEchoEnum ?? arg_pigeon_instance!.flutterEchoEnum) + ?.call(arg_pigeon_instance!, arg_anEnum!); + return wrapResponse(result: output?.index); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApi', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApi was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApi was null, expected non-null ProxyApiTestClass.'); + final ProxyApiSuperClass? arg_aProxyApi = + (args[1] as ProxyApiSuperClass?); + assert(arg_aProxyApi != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoProxyApi was null, expected non-null ProxyApiSuperClass.'); + try { + final ProxyApiSuperClass? output = (flutterEchoProxyApi ?? + arg_pigeon_instance!.flutterEchoProxyApi) + ?.call(arg_pigeon_instance!, arg_aProxyApi!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableBool', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableBool was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableBool was null, expected non-null ProxyApiTestClass.'); + final bool? arg_aBool = (args[1] as bool?); + try { + final bool? output = (flutterEchoNullableBool ?? + arg_pigeon_instance!.flutterEchoNullableBool) + ?.call(arg_pigeon_instance!, arg_aBool); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableInt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableInt was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableInt was null, expected non-null ProxyApiTestClass.'); + final int? arg_anInt = (args[1] as int?); + try { + final int? output = (flutterEchoNullableInt ?? + arg_pigeon_instance!.flutterEchoNullableInt) + ?.call(arg_pigeon_instance!, arg_anInt); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableDouble', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableDouble was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableDouble was null, expected non-null ProxyApiTestClass.'); + final double? arg_aDouble = (args[1] as double?); + try { + final double? output = (flutterEchoNullableDouble ?? + arg_pigeon_instance!.flutterEchoNullableDouble) + ?.call(arg_pigeon_instance!, arg_aDouble); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableString', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableString was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableString was null, expected non-null ProxyApiTestClass.'); + final String? arg_aString = (args[1] as String?); + try { + final String? output = (flutterEchoNullableString ?? + arg_pigeon_instance!.flutterEchoNullableString) + ?.call(arg_pigeon_instance!, arg_aString); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableUint8List', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableUint8List was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableUint8List was null, expected non-null ProxyApiTestClass.'); + final Uint8List? arg_aList = (args[1] as Uint8List?); + try { + final Uint8List? output = (flutterEchoNullableUint8List ?? + arg_pigeon_instance!.flutterEchoNullableUint8List) + ?.call(arg_pigeon_instance!, arg_aList); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableList', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableList was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableList was null, expected non-null ProxyApiTestClass.'); + final List? arg_aList = + (args[1] as List?)?.cast(); + try { + final List? output = (flutterEchoNullableList ?? + arg_pigeon_instance!.flutterEchoNullableList) + ?.call(arg_pigeon_instance!, arg_aList); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableMap', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableMap was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableMap was null, expected non-null ProxyApiTestClass.'); + final Map? arg_aMap = + (args[1] as Map?)?.cast(); + try { + final Map? output = (flutterEchoNullableMap ?? + arg_pigeon_instance!.flutterEchoNullableMap) + ?.call(arg_pigeon_instance!, arg_aMap); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableEnum', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableEnum was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableEnum was null, expected non-null ProxyApiTestClass.'); + final ProxyApiTestEnum? arg_anEnum = + args[1] == null ? null : ProxyApiTestEnum.values[args[1]! as int]; + try { + final ProxyApiTestEnum? output = (flutterEchoNullableEnum ?? + arg_pigeon_instance!.flutterEchoNullableEnum) + ?.call(arg_pigeon_instance!, arg_anEnum); + return wrapResponse(result: output?.index); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableProxyApi', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableProxyApi was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableProxyApi was null, expected non-null ProxyApiTestClass.'); + final ProxyApiSuperClass? arg_aProxyApi = + (args[1] as ProxyApiSuperClass?); + try { + final ProxyApiSuperClass? output = (flutterEchoNullableProxyApi ?? + arg_pigeon_instance!.flutterEchoNullableProxyApi) + ?.call(arg_pigeon_instance!, arg_aProxyApi); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoopAsync', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoopAsync was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterNoopAsync was null, expected non-null ProxyApiTestClass.'); + try { + await (flutterNoopAsync ?? arg_pigeon_instance!.flutterNoopAsync) + ?.call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoAsyncString', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoAsyncString was null.'); + final List args = (message as List?)!; + final ProxyApiTestClass? arg_pigeon_instance = + (args[0] as ProxyApiTestClass?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoAsyncString was null, expected non-null ProxyApiTestClass.'); + final String? arg_aString = (args[1] as String?); + assert(arg_aString != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoAsyncString was null, expected non-null String.'); + try { + final String? output = await (flutterEchoAsyncString ?? + arg_pigeon_instance!.flutterEchoAsyncString) + ?.call(arg_pigeon_instance!, arg_aString!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + ProxyApiSuperClass __pigeon_attachedField() { + final ProxyApiSuperClass __pigeon_instance = + ProxyApiSuperClass.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(__pigeon_instance); + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.attachedField'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, __pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + return __pigeon_instance; + } + + static ProxyApiSuperClass __pigeon_staticAttachedField() { + final ProxyApiSuperClass __pigeon_instance = + ProxyApiSuperClass.pigeon_detached(); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger __pigeon_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int __pigeon_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(__pigeon_instance); + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.staticAttachedField'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + return __pigeon_instance; + } + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic calling. + Future noop() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.noop'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns an error, to test error handling. + Future throwError() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Returns an error from a void function, to test error handling. + Future throwErrorFromVoid() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwErrorFromVoid'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns a Flutter error, to test error handling. + Future throwFlutterError() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwFlutterError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Returns passed in int. + Future echoInt(int anInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + /// Returns passed in double. + Future echoDouble(double aDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Returns the passed in boolean. + Future echoBool(bool aBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Returns the passed in string. + Future echoString(String aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + /// Returns the passed in Uint8List. + Future echoUint8List(Uint8List aUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Uint8List?)!; + } + } + + /// Returns the passed in generic Object. + Future echoObject(Object anObject) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoObject'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anObject]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return __pigeon_replyList[0]!; + } + } + + /// Returns the passed list, to test serialization and deserialization. + Future> echoList(List aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } + + /// Returns the passed list with ProxyApis, to test serialization and + /// deserialization. + Future> echoProxyApiList( + List aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoProxyApiList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)! + .cast(); + } + } + + /// Returns the passed map, to test serialization and deserialization. + Future> echoMap(Map aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + /// Returns the passed map with ProxyApis, to test serialization and + /// deserialization. + Future> echoProxyApiMap( + Map aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoProxyApiMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + /// Returns the passed enum to test serialization and deserialization. + Future echoEnum(ProxyApiTestEnum anEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anEnum.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + /// Returns the passed ProxyApi to test serialization and deserialization. + Future echoProxyApi(ProxyApiSuperClass aProxyApi) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoProxyApi'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aProxyApi]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as ProxyApiSuperClass?)!; + } + } + + /// Returns passed in int. + Future echoNullableInt(int? aNullableInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?); + } + } + + /// Returns passed in double. + Future echoNullableDouble(double? aNullableDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as double?); + } + } + + /// Returns the passed in boolean. + Future echoNullableBool(bool? aNullableBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as bool?); + } + } + + /// Returns the passed in string. + Future echoNullableString(String? aNullableString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } + + /// Returns the passed in Uint8List. + Future echoNullableUint8List( + Uint8List? aNullableUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Uint8List?); + } + } + + /// Returns the passed in generic Object. + Future echoNullableObject(Object? aNullableObject) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableObject'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableObject]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Returns the passed list, to test serialization and deserialization. + Future?> echoNullableList(List? aNullableList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as List?)?.cast(); + } + } + + /// Returns the passed map, to test serialization and deserialization. + Future?> echoNullableMap( + Map? aNullableMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Map?) + ?.cast(); + } + } + + Future echoNullableEnum( + ProxyApiTestEnum? aNullableEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableEnum?.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?) == null + ? null + : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + /// Returns the passed ProxyApi to test serialization and deserialization. + Future echoNullableProxyApi( + ProxyApiSuperClass? aNullableProxyApi) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoNullableProxyApi'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aNullableProxyApi]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as ProxyApiSuperClass?); + } + } + + /// A no-op function taking no arguments and returning no value, to sanity + /// test basic asynchronous calling. + Future noopAsync() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.noopAsync'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns passed in int asynchronously. + Future echoAsyncInt(int anInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + /// Returns passed in double asynchronously. + Future echoAsyncDouble(double aDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Returns the passed in boolean asynchronously. + Future echoAsyncBool(bool aBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Returns the passed string asynchronously. + Future echoAsyncString(String aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + /// Returns the passed in Uint8List asynchronously. + Future echoAsyncUint8List(Uint8List aUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Uint8List?)!; + } + } + + /// Returns the passed in generic Object asynchronously. + Future echoAsyncObject(Object anObject) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncObject'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anObject]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return __pigeon_replyList[0]!; + } + } + + /// Returns the passed list, to test asynchronous serialization and deserialization. + Future> echoAsyncList(List aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } + + /// Returns the passed map, to test asynchronous serialization and deserialization. + Future> echoAsyncMap(Map aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + /// Returns the passed enum, to test asynchronous serialization and deserialization. + Future echoAsyncEnum(ProxyApiTestEnum anEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anEnum.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + /// Responds with an error from an async function returning a value. + Future throwAsyncError() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwAsyncError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Responds with an error from an async void function. + Future throwAsyncErrorFromVoid() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwAsyncErrorFromVoid'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Responds with a Flutter error from an async function returning a value. + Future throwAsyncFlutterError() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.throwAsyncFlutterError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Returns passed in int asynchronously. + Future echoAsyncNullableInt(int? anInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?); + } + } + + /// Returns passed in double asynchronously. + Future echoAsyncNullableDouble(double? aDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as double?); + } + } + + /// Returns the passed in boolean asynchronously. + Future echoAsyncNullableBool(bool? aBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as bool?); + } + } + + /// Returns the passed string asynchronously. + Future echoAsyncNullableString(String? aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } + + /// Returns the passed in Uint8List asynchronously. + Future echoAsyncNullableUint8List(Uint8List? aUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Uint8List?); + } + } + + /// Returns the passed in generic Object asynchronously. + Future echoAsyncNullableObject(Object? anObject) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableObject'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anObject]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + /// Returns the passed list, to test asynchronous serialization and deserialization. + Future?> echoAsyncNullableList(List? aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as List?)?.cast(); + } + } + + /// Returns the passed map, to test asynchronous serialization and deserialization. + Future?> echoAsyncNullableMap( + Map? aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Map?) + ?.cast(); + } + } + + /// Returns the passed enum, to test asynchronous serialization and deserialization. + Future echoAsyncNullableEnum( + ProxyApiTestEnum? anEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoAsyncNullableEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anEnum?.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?) == null + ? null + : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + static Future staticNoop({ + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.staticNoop'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + static Future echoStaticString( + String aString, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.echoStaticString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + static Future staticAsyncNoop({ + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.staticAsyncNoop'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future callFlutterNoop() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterNoop'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future callFlutterThrowError() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterThrowError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return __pigeon_replyList[0]; + } + } + + Future callFlutterThrowErrorFromVoid() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterThrowErrorFromVoid'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future callFlutterEchoBool(bool aBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future callFlutterEchoInt(int anInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + Future callFlutterEchoDouble(double aDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + Future callFlutterEchoString(String aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + Future callFlutterEchoUint8List(Uint8List aUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Uint8List?)!; + } + } + + Future> callFlutterEchoList(List aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } + + Future> callFlutterEchoProxyApiList( + List aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoProxyApiList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)! + .cast(); + } + } + + Future> callFlutterEchoMap( + Map aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + Future> callFlutterEchoProxyApiMap( + Map aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoProxyApiMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Map?)! + .cast(); + } + } + + Future callFlutterEchoEnum(ProxyApiTestEnum anEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anEnum.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + Future callFlutterEchoProxyApi( + ProxyApiSuperClass aProxyApi) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoProxyApi'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aProxyApi]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as ProxyApiSuperClass?)!; + } + } + + Future callFlutterEchoNullableBool(bool? aBool) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableBool'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aBool]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as bool?); + } + } + + Future callFlutterEchoNullableInt(int? anInt) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableInt'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anInt]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?); + } + } + + Future callFlutterEchoNullableDouble(double? aDouble) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableDouble'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aDouble]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as double?); + } + } + + Future callFlutterEchoNullableString(String? aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as String?); + } + } + + Future callFlutterEchoNullableUint8List( + Uint8List? aUint8List) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableUint8List'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aUint8List]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Uint8List?); + } + } + + Future?> callFlutterEchoNullableList( + List? aList) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableList'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as List?)?.cast(); + } + } + + Future?> callFlutterEchoNullableMap( + Map? aMap) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableMap'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as Map?) + ?.cast(); + } + } + + Future callFlutterEchoNullableEnum( + ProxyApiTestEnum? anEnum) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableEnum'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, anEnum?.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as int?) == null + ? null + : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + } + } + + Future callFlutterEchoNullableProxyApi( + ProxyApiSuperClass? aProxyApi) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoNullableProxyApi'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, aProxyApi]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as ProxyApiSuperClass?); + } + } + + Future callFlutterNoopAsync() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterNoopAsync'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future callFlutterEchoAsyncString(String aString) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiTestClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.callFlutterEchoAsyncString'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, aString]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + @override + ProxyApiTestClass pigeon_copy() { + return ProxyApiTestClass.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + aBool: aBool, + anInt: anInt, + aDouble: aDouble, + aString: aString, + aUint8List: aUint8List, + aList: aList, + aMap: aMap, + anEnum: anEnum, + aProxyApi: aProxyApi, + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableDouble: aNullableDouble, + aNullableString: aNullableString, + aNullableUint8List: aNullableUint8List, + aNullableList: aNullableList, + aNullableMap: aNullableMap, + aNullableEnum: aNullableEnum, + aNullableProxyApi: aNullableProxyApi, + anInterfaceMethod: anInterfaceMethod, + flutterNoop: flutterNoop, + flutterThrowError: flutterThrowError, + flutterThrowErrorFromVoid: flutterThrowErrorFromVoid, + flutterEchoBool: flutterEchoBool, + flutterEchoInt: flutterEchoInt, + flutterEchoDouble: flutterEchoDouble, + flutterEchoString: flutterEchoString, + flutterEchoUint8List: flutterEchoUint8List, + flutterEchoList: flutterEchoList, + flutterEchoProxyApiList: flutterEchoProxyApiList, + flutterEchoMap: flutterEchoMap, + flutterEchoProxyApiMap: flutterEchoProxyApiMap, + flutterEchoEnum: flutterEchoEnum, + flutterEchoProxyApi: flutterEchoProxyApi, + flutterEchoNullableBool: flutterEchoNullableBool, + flutterEchoNullableInt: flutterEchoNullableInt, + flutterEchoNullableDouble: flutterEchoNullableDouble, + flutterEchoNullableString: flutterEchoNullableString, + flutterEchoNullableUint8List: flutterEchoNullableUint8List, + flutterEchoNullableList: flutterEchoNullableList, + flutterEchoNullableMap: flutterEchoNullableMap, + flutterEchoNullableEnum: flutterEchoNullableEnum, + flutterEchoNullableProxyApi: flutterEchoNullableProxyApi, + flutterNoopAsync: flutterNoopAsync, + flutterEchoAsyncString: flutterEchoAsyncString, + ); + } +} + +/// ProxyApi to serve as a super class to the core ProxyApi class. +class ProxyApiSuperClass extends PigeonProxyApiBaseClass { + ProxyApiSuperClass({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiSuperClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiSuperClass.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [ProxyApiSuperClass] 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 + ProxyApiSuperClass.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecProxyApiSuperClass = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ProxyApiSuperClass Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiSuperClass.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiSuperClass.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.pigeon_integration_tests.ProxyApiSuperClass.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ProxyApiSuperClass.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())); + } + }); + } + } + } + + Future aSuperMethod() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecProxyApiSuperClass; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiSuperClass.aSuperMethod'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + ProxyApiSuperClass pigeon_copy() { + return ProxyApiSuperClass.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// ProxyApi to serve as an interface to the core ProxyApi class. +class ProxyApiInterface extends PigeonProxyApiBaseClass { + /// Constructs [ProxyApiInterface] 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 + ProxyApiInterface.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.anInterfaceMethod, + }); + + /// Callback method. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final ProxyApiInterface instance = ProxyApiInterface( + /// anInterfaceMethod: (ProxyApiInterface pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(ProxyApiInterface pigeon_instance)? anInterfaceMethod; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ProxyApiInterface Function()? pigeon_newInstance, + void Function(ProxyApiInterface pigeon_instance)? anInterfaceMethod, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiInterface.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiInterface.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.pigeon_integration_tests.ProxyApiInterface.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ProxyApiInterface.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())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.ProxyApiInterface.anInterfaceMethod', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiInterface.anInterfaceMethod was null.'); + final List args = (message as List?)!; + final ProxyApiInterface? arg_pigeon_instance = + (args[0] as ProxyApiInterface?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiInterface.anInterfaceMethod was null, expected non-null ProxyApiInterface.'); + try { + (anInterfaceMethod ?? arg_pigeon_instance!.anInterfaceMethod) + ?.call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + ProxyApiInterface pigeon_copy() { + return ProxyApiInterface.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + anInterfaceMethod: anInterfaceMethod, + ); + } +} diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/instance_manager_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/instance_manager_test.dart new file mode 100644 index 000000000000..aadce1b81d7b --- /dev/null +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/instance_manager_test.dart @@ -0,0 +1,168 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file specifically tests the test PigeonInstanceManager generated by core_tests. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_test_plugin_code/src/generated/proxy_api_tests.gen.dart'; + +void main() { + group('InstanceManager', () { + test('addHostCreatedInstance', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + + expect(instanceManager.getIdentifier(object), 0); + expect( + instanceManager.getInstanceWithWeakReference(0), + object, + ); + }); + + test('addHostCreatedInstance prevents already used objects and ids', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + + expect( + () => instanceManager.addHostCreatedInstance(object, 0), + throwsAssertionError, + ); + + expect( + () => instanceManager.addHostCreatedInstance( + CopyableObject(pigeon_instanceManager: instanceManager), + 0, + ), + throwsAssertionError, + ); + }); + + test('addFlutterCreatedInstance', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addDartCreatedInstance(object); + + final int? instanceId = instanceManager.getIdentifier(object); + expect(instanceId, isNotNull); + expect( + instanceManager.getInstanceWithWeakReference(instanceId!), + object, + ); + }); + + test('removeWeakReference', () { + int? weakInstanceId; + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (int instanceId) { + weakInstanceId = instanceId; + }); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + + expect(instanceManager.removeWeakReference(object), 0); + expect( + instanceManager.getInstanceWithWeakReference(0), + isA(), + ); + expect(weakInstanceId, 0); + }); + + test('removeWeakReference removes only weak reference', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + + expect(instanceManager.removeWeakReference(object), 0); + final CopyableObject copy = instanceManager.getInstanceWithWeakReference( + 0, + )!; + expect(identical(object, copy), isFalse); + }); + + test('removeStrongReference', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + instanceManager.removeWeakReference(object); + expect(instanceManager.remove(0), isA()); + expect(instanceManager.containsIdentifier(0), isFalse); + }); + + test('removeStrongReference removes only strong reference', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + expect(instanceManager.remove(0), isA()); + expect( + instanceManager.getInstanceWithWeakReference(0), + object, + ); + }); + + test('getInstance can add a new weak reference', () { + final PigeonInstanceManager instanceManager = + PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + + final CopyableObject object = CopyableObject( + pigeon_instanceManager: instanceManager, + ); + + instanceManager.addHostCreatedInstance(object, 0); + instanceManager.removeWeakReference(object); + + final CopyableObject newWeakCopy = + instanceManager.getInstanceWithWeakReference( + 0, + )!; + expect(identical(object, newWeakCopy), isFalse); + }); + }); +} + +class CopyableObject extends PigeonProxyApiBaseClass { + // ignore: non_constant_identifier_names + CopyableObject({super.pigeon_instanceManager}); + + @override + // ignore: non_constant_identifier_names + CopyableObject pigeon_copy() { + return CopyableObject(pigeon_instanceManager: pigeon_instanceManager); + } +} diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt index c3ec9d98caf6..002c621784a8 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt @@ -20,7 +20,7 @@ private fun wrapResult(result: Any?): List { } private fun wrapError(exception: Throwable): List { - if (exception is CoreTestsError) { + if (exception is FlutterError) { return listOf(exception.code, exception.message, exception.details) } else { return listOf( @@ -30,24 +30,11 @@ private fun wrapError(exception: Throwable): List { } } -private fun createConnectionError(channelName: String): CoreTestsError { - return CoreTestsError( +private fun createConnectionError(channelName: String): FlutterError { + return FlutterError( "channel-error", "Unable to establish connection on channel: '$channelName'.", "") } -/** - * 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 CoreTestsError( - val code: String, - override val message: String? = null, - val details: Any? = null -) : Throwable() - enum class AnEnum(val raw: Int) { ONE(0), TWO(1), @@ -155,7 +142,8 @@ data class AllNullableTypes( val nullableMapWithObject: Map? = null, val aNullableEnum: AnEnum? = null, val aNullableString: String? = null, - val aNullableObject: Any? = null + val aNullableObject: Any? = null, + val allNullableTypes: AllNullableTypes? = null ) { companion object { @Suppress("UNCHECKED_CAST") @@ -176,7 +164,96 @@ data class AllNullableTypes( val aNullableEnum: AnEnum? = (list[13] as Int?)?.let { AnEnum.ofRaw(it) } val aNullableString = list[14] as String? val aNullableObject = list[15] + val allNullableTypes: AllNullableTypes? = + (list[16] as List?)?.let { AllNullableTypes.fromList(it) } return AllNullableTypes( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableList, + aNullableMap, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes) + } + } + + fun toList(): List { + return listOf( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableList, + aNullableMap, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum?.raw, + aNullableString, + aNullableObject, + allNullableTypes?.toList(), + ) + } +} + +/** + * The primary purpose for this class is to ensure coverage of Swift structs with nullable items, as + * the primary [AllNullableTypes] class is being used to test Swift classes. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class AllNullableTypesWithoutRecursion( + val aNullableBool: Boolean? = null, + val aNullableInt: Long? = null, + val aNullableInt64: Long? = null, + val aNullableDouble: Double? = null, + val aNullableByteArray: ByteArray? = null, + val aNullable4ByteArray: IntArray? = null, + val aNullable8ByteArray: LongArray? = null, + val aNullableFloatArray: DoubleArray? = null, + val aNullableList: List? = null, + val aNullableMap: Map? = null, + val nullableNestedList: List?>? = null, + val nullableMapWithAnnotations: Map? = null, + val nullableMapWithObject: Map? = null, + val aNullableEnum: AnEnum? = null, + val aNullableString: String? = null, + val aNullableObject: Any? = null +) { + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): AllNullableTypesWithoutRecursion { + val aNullableBool = list[0] as Boolean? + val aNullableInt = list[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableInt64 = list[2].let { if (it is Int) it.toLong() else it as Long? } + val aNullableDouble = list[3] as Double? + val aNullableByteArray = list[4] as ByteArray? + val aNullable4ByteArray = list[5] as IntArray? + val aNullable8ByteArray = list[6] as LongArray? + val aNullableFloatArray = list[7] as DoubleArray? + val aNullableList = list[8] as List? + val aNullableMap = list[9] as Map? + val nullableNestedList = list[10] as List?>? + val nullableMapWithAnnotations = list[11] as Map? + val nullableMapWithObject = list[12] as Map? + val aNullableEnum: AnEnum? = (list[13] as Int?)?.let { AnEnum.ofRaw(it) } + val aNullableString = list[14] as String? + val aNullableObject = list[15] + return AllNullableTypesWithoutRecursion( aNullableBool, aNullableInt, aNullableInt64, @@ -229,20 +306,24 @@ data class AllNullableTypes( */ data class AllClassesWrapper( val allNullableTypes: AllNullableTypes, + val allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = null, val allTypes: AllTypes? = null ) { companion object { @Suppress("UNCHECKED_CAST") fun fromList(list: List): AllClassesWrapper { val allNullableTypes = AllNullableTypes.fromList(list[0] as List) - val allTypes: AllTypes? = (list[1] as List?)?.let { AllTypes.fromList(it) } - return AllClassesWrapper(allNullableTypes, allTypes) + val allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = + (list[1] as List?)?.let { AllNullableTypesWithoutRecursion.fromList(it) } + val allTypes: AllTypes? = (list[2] as List?)?.let { AllTypes.fromList(it) } + return AllClassesWrapper(allNullableTypes, allNullableTypesWithoutRecursion, allTypes) } } fun toList(): List { return listOf( allNullableTypes.toList(), + allNullableTypesWithoutRecursion?.toList(), allTypes?.toList(), ) } @@ -281,9 +362,14 @@ private object HostIntegrationCoreApiCodec : StandardMessageCodec() { return (readValue(buffer) as? List)?.let { AllNullableTypes.fromList(it) } } 130.toByte() -> { - return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } + return (readValue(buffer) as? List)?.let { + AllNullableTypesWithoutRecursion.fromList(it) + } } 131.toByte() -> { + return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } + } + 132.toByte() -> { return (readValue(buffer) as? List)?.let { TestMessage.fromList(it) } } else -> super.readValueOfType(type, buffer) @@ -300,14 +386,18 @@ private object HostIntegrationCoreApiCodec : StandardMessageCodec() { stream.write(129) writeValue(stream, value.toList()) } - is AllTypes -> { + is AllNullableTypesWithoutRecursion -> { stream.write(130) writeValue(stream, value.toList()) } - is TestMessage -> { + is AllTypes -> { stream.write(131) writeValue(stream, value.toList()) } + is TestMessage -> { + stream.write(132) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -358,6 +448,10 @@ interface HostIntegrationCoreApi { fun echoRequiredInt(anInt: Long): Long /** Returns the passed object, to test serialization and deserialization. */ fun echoAllNullableTypes(everything: AllNullableTypes?): AllNullableTypes? + /** Returns the passed object, to test serialization and deserialization. */ + fun echoAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion? + ): AllNullableTypesWithoutRecursion? /** * Returns the inner `aString` value from the wrapped object, to test sending of nested objects. */ @@ -372,6 +466,12 @@ interface HostIntegrationCoreApi { aNullableInt: Long?, aNullableString: String? ): AllNullableTypes + /** Returns passed in arguments of multiple types. */ + fun sendMultipleNullableTypesWithoutRecursion( + aNullableBool: Boolean?, + aNullableInt: Long?, + aNullableString: String? + ): AllNullableTypesWithoutRecursion /** Returns passed in int. */ fun echoNullableInt(aNullableInt: Long?): Long? /** Returns passed in double. */ @@ -430,6 +530,11 @@ interface HostIntegrationCoreApi { everything: AllNullableTypes?, callback: (Result) -> Unit ) + /** Returns the passed object, to test serialization and deserialization. */ + fun echoAsyncNullableAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion?, + callback: (Result) -> Unit + ) /** Returns passed in int asynchronously. */ fun echoAsyncNullableInt(anInt: Long?, callback: (Result) -> Unit) /** Returns passed in double asynchronously. */ @@ -472,6 +577,18 @@ interface HostIntegrationCoreApi { callback: (Result) -> Unit ) + fun callFlutterEchoAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion?, + callback: (Result) -> Unit + ) + + fun callFlutterSendMultipleNullableTypesWithoutRecursion( + aNullableBool: Boolean?, + aNullableInt: Long?, + aNullableString: String?, + callback: (Result) -> Unit + ) + fun callFlutterEchoBool(aBool: Boolean, callback: (Result) -> Unit) fun callFlutterEchoInt(anInt: Long, callback: (Result) -> Unit) @@ -928,6 +1045,28 @@ interface HostIntegrationCoreApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val everythingArg = args[0] as AllNullableTypesWithoutRecursion? + var wrapped: List + try { + wrapped = listOf(api.echoAllNullableTypesWithoutRecursion(everythingArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel( @@ -999,6 +1138,33 @@ interface HostIntegrationCoreApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val aNullableBoolArg = args[0] as Boolean? + val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableStringArg = args[2] as String? + var wrapped: List + try { + wrapped = + listOf( + api.sendMultipleNullableTypesWithoutRecursion( + aNullableBoolArg, aNullableIntArg, aNullableStringArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel( @@ -1592,6 +1758,31 @@ interface HostIntegrationCoreApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableAllNullableTypesWithoutRecursion", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val everythingArg = args[0] as AllNullableTypesWithoutRecursion? + api.echoAsyncNullableAllNullableTypesWithoutRecursion(everythingArg) { + result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel( @@ -1949,6 +2140,59 @@ interface HostIntegrationCoreApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoAllNullableTypesWithoutRecursion", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val everythingArg = args[0] as AllNullableTypesWithoutRecursion? + api.callFlutterEchoAllNullableTypesWithoutRecursion(everythingArg) { + result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterSendMultipleNullableTypesWithoutRecursion", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val aNullableBoolArg = args[0] as Boolean? + val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableStringArg = args[2] as String? + api.callFlutterSendMultipleNullableTypesWithoutRecursion( + aNullableBoolArg, aNullableIntArg, aNullableStringArg) { + result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel( @@ -2348,9 +2592,14 @@ private object FlutterIntegrationCoreApiCodec : StandardMessageCodec() { return (readValue(buffer) as? List)?.let { AllNullableTypes.fromList(it) } } 130.toByte() -> { - return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } + return (readValue(buffer) as? List)?.let { + AllNullableTypesWithoutRecursion.fromList(it) + } } 131.toByte() -> { + return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } + } + 132.toByte() -> { return (readValue(buffer) as? List)?.let { TestMessage.fromList(it) } } else -> super.readValueOfType(type, buffer) @@ -2367,14 +2616,18 @@ private object FlutterIntegrationCoreApiCodec : StandardMessageCodec() { stream.write(129) writeValue(stream, value.toList()) } - is AllTypes -> { + is AllNullableTypesWithoutRecursion -> { stream.write(130) writeValue(stream, value.toList()) } - is TestMessage -> { + is AllTypes -> { stream.write(131) writeValue(stream, value.toList()) } + is TestMessage -> { + stream.write(132) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -2399,8 +2652,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(null) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { callback(Result.success(Unit)) } @@ -2417,8 +2669,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(null) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] callback(Result.success(output)) @@ -2436,8 +2687,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(null) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { callback(Result.success(Unit)) } @@ -2454,12 +2704,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(everythingArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2483,8 +2732,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(everythingArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as AllNullableTypes? callback(Result.success(output)) @@ -2511,12 +2759,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aNullableBoolArg, aNullableIntArg, aNullableStringArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2529,6 +2776,61 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { } } } + /** Returns the passed object, to test serialization and deserialization. */ + fun echoAllNullableTypesWithoutRecursion( + everythingArg: AllNullableTypesWithoutRecursion?, + callback: (Result) -> Unit + ) { + val channelName = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(everythingArg)) { + 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 { + val output = it[0] as AllNullableTypesWithoutRecursion? + callback(Result.success(output)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + /** + * Returns passed in arguments of multiple types. + * + * Tests multiple-arity FlutterApi handling. + */ + fun sendMultipleNullableTypesWithoutRecursion( + aNullableBoolArg: Boolean?, + aNullableIntArg: Long?, + aNullableStringArg: String?, + callback: (Result) -> Unit + ) { + val channelName = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(aNullableBoolArg, aNullableIntArg, aNullableStringArg)) { + 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 if (it[0] == null) { + callback( + Result.failure( + FlutterError( + "null-error", + "Flutter api returned null value for non-null return value.", + ""))) + } else { + val output = it[0] as AllNullableTypesWithoutRecursion + callback(Result.success(output)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } /** Returns the passed boolean, to test serialization and deserialization. */ fun echoBool(aBoolArg: Boolean, callback: (Result) -> Unit) { val channelName = @@ -2537,12 +2839,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aBoolArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2563,12 +2864,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(anIntArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2589,12 +2889,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aDoubleArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2615,12 +2914,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aStringArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2641,12 +2939,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aListArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2667,12 +2964,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aListArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2693,12 +2989,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aMapArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2719,12 +3014,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(anEnumArg.raw)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -2745,8 +3039,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aBoolArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as Boolean? callback(Result.success(output)) @@ -2764,8 +3057,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(anIntArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0].let { if (it is Int) it.toLong() else it as Long? } callback(Result.success(output)) @@ -2783,8 +3075,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aDoubleArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as Double? callback(Result.success(output)) @@ -2802,8 +3093,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aStringArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as String? callback(Result.success(output)) @@ -2821,8 +3111,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aListArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as ByteArray? callback(Result.success(output)) @@ -2840,8 +3129,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aListArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as List? callback(Result.success(output)) @@ -2862,8 +3150,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aMapArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = it[0] as Map? callback(Result.success(output)) @@ -2881,8 +3168,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(anEnumArg?.raw)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { val output = (it[0] as Int?)?.let { AnEnum.ofRaw(it) } callback(Result.success(output)) @@ -2903,8 +3189,7 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(null) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { callback(Result.success(Unit)) } @@ -2921,12 +3206,11 @@ class FlutterIntegrationCoreApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aStringArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -3083,12 +3367,11 @@ class FlutterSmallApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(msgArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) @@ -3108,12 +3391,11 @@ class FlutterSmallApi(private val binaryMessenger: BinaryMessenger) { channel.send(listOf(aStringArg)) { if (it is List<*>) { if (it.size > 1) { - callback( - Result.failure(CoreTestsError(it[0] as String, it[1] as String, it[2] as String?))) + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { callback( Result.failure( - CoreTestsError( + FlutterError( "null-error", "Flutter api returned null value for non-null return value.", ""))) diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt index c14f7129e40d..c64aab7d34f5 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt @@ -4,19 +4,18 @@ package com.example.test_plugin -import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin /** This plugin handles the native side of the integration tests in example/integration_test/. */ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { var flutterApi: FlutterIntegrationCoreApi? = null - override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - HostIntegrationCoreApi.setUp(binding.getBinaryMessenger(), this) - flutterApi = FlutterIntegrationCoreApi(binding.getBinaryMessenger()) + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + HostIntegrationCoreApi.setUp(binding.binaryMessenger, this) + flutterApi = FlutterIntegrationCoreApi(binding.binaryMessenger) } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {} + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} // HostIntegrationCoreApi @@ -30,6 +29,12 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { return everything } + override fun echoAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion? + ): AllNullableTypesWithoutRecursion? { + return everything + } + override fun throwError(): Any? { throw Exception("An error") } @@ -39,7 +44,7 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } override fun throwFlutterError(): Any? { - throw CoreTestsError("code", "message", "details") + throw FlutterError("code", "message", "details") } override fun echoInt(anInt: Long): Long { @@ -113,6 +118,17 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { aNullableString = aNullableString) } + override fun sendMultipleNullableTypesWithoutRecursion( + aNullableBool: Boolean?, + aNullableInt: Long?, + aNullableString: String? + ): AllNullableTypesWithoutRecursion { + return AllNullableTypesWithoutRecursion( + aNullableBool = aNullableBool, + aNullableInt = aNullableInt, + aNullableString = aNullableString) + } + override fun echoNullableInt(aNullableInt: Long?): Long? { return aNullableInt } @@ -170,7 +186,7 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } override fun throwAsyncFlutterError(callback: (Result) -> Unit) { - callback(Result.failure(CoreTestsError("code", "message", "details"))) + callback(Result.failure(FlutterError("code", "message", "details"))) } override fun echoAsyncAllTypes(everything: AllTypes, callback: (Result) -> Unit) { @@ -184,6 +200,13 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { callback(Result.success(everything)) } + override fun echoAsyncNullableAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion?, + callback: (Result) -> Unit + ) { + callback(Result.success(everything)) + } + override fun echoAsyncInt(anInt: Long, callback: (Result) -> Unit) { callback(Result.success(anInt)) } @@ -292,6 +315,25 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } } + override fun callFlutterEchoAllNullableTypesWithoutRecursion( + everything: AllNullableTypesWithoutRecursion?, + callback: (Result) -> Unit + ) { + flutterApi!!.echoAllNullableTypesWithoutRecursion(everything) { echo -> callback(echo) } + } + + override fun callFlutterSendMultipleNullableTypesWithoutRecursion( + aNullableBool: Boolean?, + aNullableInt: Long?, + aNullableString: String?, + callback: (Result) -> Unit + ) { + flutterApi!!.sendMultipleNullableTypesWithoutRecursion( + aNullableBool, aNullableInt, aNullableString) { echo -> + callback(echo) + } + } + override fun callFlutterEchoBool(aBool: Boolean, callback: (Result) -> Unit) { flutterApi!!.echoBool(aBool) { echo -> callback(echo) } } diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt index 7a506d3d1307..81207b03f586 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt @@ -160,6 +160,7 @@ internal class AllDatatypesTest : TestCase() { null, null, null, + null, null) val everything2 = AllNullableTypes.fromList(list2) diff --git a/packages/pigeon/platform_tests/test_plugin/example/windows/flutter/CMakeLists.txt b/packages/pigeon/platform_tests/test_plugin/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/windows/flutter/CMakeLists.txt +++ b/packages/pigeon/platform_tests/test_plugin/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift index 62682b44c2c9..3a45a12a40fe 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift @@ -128,7 +128,139 @@ struct AllTypes { /// A class containing all supported nullable types. /// /// Generated class from Pigeon that represents data sent in messages. -struct AllNullableTypes { +class AllNullableTypes { + init( + aNullableBool: Bool? = nil, + aNullableInt: Int64? = nil, + aNullableInt64: Int64? = nil, + aNullableDouble: Double? = nil, + aNullableByteArray: FlutterStandardTypedData? = nil, + aNullable4ByteArray: FlutterStandardTypedData? = nil, + aNullable8ByteArray: FlutterStandardTypedData? = nil, + aNullableFloatArray: FlutterStandardTypedData? = nil, + aNullableList: [Any?]? = nil, + aNullableMap: [AnyHashable: Any?]? = nil, + nullableNestedList: [[Bool?]?]? = nil, + nullableMapWithAnnotations: [String?: String?]? = nil, + nullableMapWithObject: [String?: Any?]? = nil, + aNullableEnum: AnEnum? = nil, + aNullableString: String? = nil, + aNullableObject: Any? = nil, + allNullableTypes: AllNullableTypes? = nil + ) { + self.aNullableBool = aNullableBool + self.aNullableInt = aNullableInt + self.aNullableInt64 = aNullableInt64 + self.aNullableDouble = aNullableDouble + self.aNullableByteArray = aNullableByteArray + self.aNullable4ByteArray = aNullable4ByteArray + self.aNullable8ByteArray = aNullable8ByteArray + self.aNullableFloatArray = aNullableFloatArray + self.aNullableList = aNullableList + self.aNullableMap = aNullableMap + self.nullableNestedList = nullableNestedList + self.nullableMapWithAnnotations = nullableMapWithAnnotations + self.nullableMapWithObject = nullableMapWithObject + self.aNullableEnum = aNullableEnum + self.aNullableString = aNullableString + self.aNullableObject = aNullableObject + self.allNullableTypes = allNullableTypes + } + var aNullableBool: Bool? + var aNullableInt: Int64? + var aNullableInt64: Int64? + var aNullableDouble: Double? + var aNullableByteArray: FlutterStandardTypedData? + var aNullable4ByteArray: FlutterStandardTypedData? + var aNullable8ByteArray: FlutterStandardTypedData? + var aNullableFloatArray: FlutterStandardTypedData? + var aNullableList: [Any?]? + var aNullableMap: [AnyHashable: Any?]? + var nullableNestedList: [[Bool?]?]? + var nullableMapWithAnnotations: [String?: String?]? + var nullableMapWithObject: [String?: Any?]? + var aNullableEnum: AnEnum? + var aNullableString: String? + var aNullableObject: Any? + var allNullableTypes: AllNullableTypes? + + static func fromList(_ list: [Any?]) -> AllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(list[0]) + let aNullableInt: Int64? = + isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + let aNullableInt64: Int64? = + isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) + let aNullableList: [Any?]? = nilOrValue(list[8]) + let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) + var aNullableEnum: AnEnum? = nil + let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) + if let aNullableEnumRawValue = aNullableEnumEnumVal { + aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! + } + let aNullableString: String? = nilOrValue(list[14]) + let aNullableObject: Any? = list[15] + var allNullableTypes: AllNullableTypes? = nil + if let allNullableTypesList: [Any?] = nilOrValue(list[16]) { + allNullableTypes = AllNullableTypes.fromList(allNullableTypesList) + } + + return AllNullableTypes( + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableInt64: aNullableInt64, + aNullableDouble: aNullableDouble, + aNullableByteArray: aNullableByteArray, + aNullable4ByteArray: aNullable4ByteArray, + aNullable8ByteArray: aNullable8ByteArray, + aNullableFloatArray: aNullableFloatArray, + aNullableList: aNullableList, + aNullableMap: aNullableMap, + nullableNestedList: nullableNestedList, + nullableMapWithAnnotations: nullableMapWithAnnotations, + nullableMapWithObject: nullableMapWithObject, + aNullableEnum: aNullableEnum, + aNullableString: aNullableString, + aNullableObject: aNullableObject, + allNullableTypes: allNullableTypes + ) + } + func toList() -> [Any?] { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableList, + aNullableMap, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum?.rawValue, + aNullableString, + aNullableObject, + allNullableTypes?.toList(), + ] + } +} + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct AllNullableTypesWithoutRecursion { var aNullableBool: Bool? = nil var aNullableInt: Int64? = nil var aNullableInt64: Int64? = nil @@ -146,7 +278,7 @@ struct AllNullableTypes { var aNullableString: String? = nil var aNullableObject: Any? = nil - static func fromList(_ list: [Any?]) -> AllNullableTypes? { + static func fromList(_ list: [Any?]) -> AllNullableTypesWithoutRecursion? { let aNullableBool: Bool? = nilOrValue(list[0]) let aNullableInt: Int64? = isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) @@ -170,7 +302,7 @@ struct AllNullableTypes { let aNullableString: String? = nilOrValue(list[14]) let aNullableObject: Any? = list[15] - return AllNullableTypes( + return AllNullableTypesWithoutRecursion( aNullableBool: aNullableBool, aNullableInt: aNullableInt, aNullableInt64: aNullableInt64, @@ -220,23 +352,31 @@ struct AllNullableTypes { /// Generated class from Pigeon that represents data sent in messages. struct AllClassesWrapper { var allNullableTypes: AllNullableTypes + var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil var allTypes: AllTypes? = nil static func fromList(_ list: [Any?]) -> AllClassesWrapper? { let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])! + var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil + if let allNullableTypesWithoutRecursionList: [Any?] = nilOrValue(list[1]) { + allNullableTypesWithoutRecursion = AllNullableTypesWithoutRecursion.fromList( + allNullableTypesWithoutRecursionList) + } var allTypes: AllTypes? = nil - if let allTypesList: [Any?] = nilOrValue(list[1]) { + if let allTypesList: [Any?] = nilOrValue(list[2]) { allTypes = AllTypes.fromList(allTypesList) } return AllClassesWrapper( allNullableTypes: allNullableTypes, + allNullableTypesWithoutRecursion: allNullableTypesWithoutRecursion, allTypes: allTypes ) } func toList() -> [Any?] { return [ allNullableTypes.toList(), + allNullableTypesWithoutRecursion?.toList(), allTypes?.toList(), ] } @@ -270,8 +410,10 @@ private class HostIntegrationCoreApiCodecReader: FlutterStandardReader { case 129: return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 131: + return AllTypes.fromList(self.readValue() as! [Any?]) + case 132: return TestMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -287,12 +429,15 @@ private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter { } else if let value = value as? AllNullableTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllTypes { super.writeByte(131) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(132) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -358,6 +503,9 @@ protocol HostIntegrationCoreApi { func echoRequired(_ anInt: Int64) throws -> Int64 /// Returns the passed object, to test serialization and deserialization. func echo(_ everything: AllNullableTypes?) throws -> AllNullableTypes? + /// Returns the passed object, to test serialization and deserialization. + func echo(_ everything: AllNullableTypesWithoutRecursion?) throws + -> AllNullableTypesWithoutRecursion? /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. func extractNestedNullableString(from wrapper: AllClassesWrapper) throws -> String? @@ -368,6 +516,10 @@ protocol HostIntegrationCoreApi { func sendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? ) throws -> AllNullableTypes + /// Returns passed in arguments of multiple types. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? + ) throws -> AllNullableTypesWithoutRecursion /// Returns passed in int. func echo(_ aNullableInt: Int64?) throws -> Int64? /// Returns passed in double. @@ -425,6 +577,10 @@ protocol HostIntegrationCoreApi { func echoAsync( _ everything: AllNullableTypes?, completion: @escaping (Result) -> Void) + /// Returns the passed object, to test serialization and deserialization. + func echoAsync( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) /// Returns passed in int asynchronously. func echoAsyncNullable(_ anInt: Int64?, completion: @escaping (Result) -> Void) /// Returns passed in double asynchronously. @@ -457,6 +613,12 @@ protocol HostIntegrationCoreApi { func callFlutterSendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, completion: @escaping (Result) -> Void) + func callFlutterEcho( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) + func callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, + completion: @escaping (Result) -> Void) func callFlutterEcho(_ aBool: Bool, completion: @escaping (Result) -> Void) func callFlutterEcho(_ anInt: Int64, completion: @escaping (Result) -> Void) func callFlutterEcho(_ aDouble: Double, completion: @escaping (Result) -> Void) @@ -830,6 +992,25 @@ class HostIntegrationCoreApiSetup { } else { echoAllNullableTypesChannel.setMessageHandler(nil) } + /// Returns the passed object, to test serialization and deserialization. + let echoAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + echoAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + do { + let result = try api.echo(everythingArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + echoAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. let extractNestedNullableStringChannel = FlutterBasicMessageChannel( @@ -894,6 +1075,30 @@ class HostIntegrationCoreApiSetup { } else { sendMultipleNullableTypesChannel.setMessageHandler(nil) } + /// Returns passed in arguments of multiple types. + let sendMultipleNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + sendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let aNullableBoolArg: Bool? = nilOrValue(args[0]) + let aNullableIntArg: Int64? = + isNullish(args[1]) + ? nil : (args[1] is Int64? ? args[1] as! Int64? : Int64(args[1] as! Int32)) + let aNullableStringArg: String? = nilOrValue(args[2]) + do { + let result = try api.sendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBoolArg, anInt: aNullableIntArg, aString: aNullableStringArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + sendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns passed in int. let echoNullableIntChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoNullableInt", @@ -1394,6 +1599,27 @@ class HostIntegrationCoreApiSetup { } else { echoAsyncNullableAllNullableTypesChannel.setMessageHandler(nil) } + /// Returns the passed object, to test serialization and deserialization. + let echoAsyncNullableAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + echoAsyncNullableAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + api.echoAsync(everythingArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + echoAsyncNullableAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns passed in int asynchronously. let echoAsyncNullableIntChannel = FlutterBasicMessageChannel( name: @@ -1704,6 +1930,53 @@ class HostIntegrationCoreApiSetup { } else { callFlutterSendMultipleNullableTypesChannel.setMessageHandler(nil) } + let callFlutterEchoAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + callFlutterEchoAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + api.callFlutterEcho(everythingArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + callFlutterEchoAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } + let callFlutterSendMultipleNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterSendMultipleNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + callFlutterSendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler { + message, reply in + let args = message as! [Any?] + let aNullableBoolArg: Bool? = nilOrValue(args[0]) + let aNullableIntArg: Int64? = + isNullish(args[1]) + ? nil : (args[1] is Int64? ? args[1] as! Int64? : Int64(args[1] as! Int32)) + let aNullableStringArg: String? = nilOrValue(args[2]) + api.callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBoolArg, anInt: aNullableIntArg, aString: aNullableStringArg + ) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + callFlutterSendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } let callFlutterEchoBoolChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoBool", @@ -2034,8 +2307,10 @@ private class FlutterIntegrationCoreApiCodecReader: FlutterStandardReader { case 129: return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 131: + return AllTypes.fromList(self.readValue() as! [Any?]) + case 132: return TestMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -2051,12 +2326,15 @@ private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter { } else if let value = value as? AllNullableTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllTypes { super.writeByte(131) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(132) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -2104,6 +2382,17 @@ protocol FlutterIntegrationCoreApiProtocol { aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, completion: @escaping (Result) -> Void) + /// Returns the passed object, to test serialization and deserialization. + func echoNullable( + _ everythingArg: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, + aString aNullableStringArg: String?, + completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. @@ -2318,6 +2607,66 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } } + /// Returns the passed object, to test serialization and deserialization. + func echoNullable( + _ everythingArg: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([everythingArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(FlutterError(code: code, message: message, details: details))) + } else { + let result: AllNullableTypesWithoutRecursion? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, + aString aNullableStringArg: String?, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([aNullableBoolArg, aNullableIntArg, aNullableStringArg] as [Any?]) { + response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(FlutterError(code: code, message: message, details: details))) + } else if listResponse[0] == nil { + completion( + .failure( + FlutterError( + code: "null-error", + message: "Flutter api returned null value for non-null return value.", details: ""))) + } else { + let result = listResponse[0] as! AllNullableTypesWithoutRecursion + completion(.success(result)) + } + } + } /// Returns the passed boolean, to test serialization and deserialization. func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { let channelName: String = diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift index 0d04c9544ca1..d444e5c0bacc 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift @@ -10,6 +10,7 @@ extension FlutterError: Error {} /// This plugin handles the native side of the integration tests in /// example/integration_test/. public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { + var flutterAPI: FlutterIntegrationCoreApi public static func register(with registrar: FlutterPluginRegistrar) { @@ -34,6 +35,11 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { func echo(_ everything: AllNullableTypes?) -> AllNullableTypes? { return everything } + func echo(_ everything: AllNullableTypesWithoutRecursion?) throws + -> AllNullableTypesWithoutRecursion? + { + return everything + } func throwError() throws -> Any? { throw FlutterError(code: "code", message: "message", details: "details") @@ -103,6 +109,14 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return someThings } + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? + ) throws -> AllNullableTypesWithoutRecursion { + let someThings = AllNullableTypesWithoutRecursion( + aNullableBool: aNullableBool, aNullableInt: aNullableInt, aNullableString: aNullableString) + return someThings + } + func echo(_ aNullableInt: Int64?) -> Int64? { return aNullableInt } @@ -186,6 +200,13 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(everything)) } + func echoAsync( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + completion(.success(everything)) + } + func echoAsync(_ anInt: Int64, completion: @escaping (Result) -> Void) { completion(.success(anInt)) } @@ -331,6 +352,20 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } + func callFlutterEcho( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + flutterAPI.echoNullable(everything) { response in + switch response { + case .success(let res): + completion(.success(res)) + case .failure(let error): + completion(.failure(error)) + } + } + } + func callFlutterSendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, @@ -351,6 +386,24 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } + func callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, + completion: @escaping (Result) -> Void + ) { + flutterAPI.sendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBool, + anInt: aNullableInt, + aString: aNullableString + ) { response in + switch response { + case .success(let res): + completion(.success(res)) + case .failure(let error): + completion(.failure(error)) + } + } + } + func callFlutterEcho(_ aBool: Bool, completion: @escaping (Result) -> Void) { flutterAPI.echo(aBool) { response in switch response { diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift index 62682b44c2c9..3a45a12a40fe 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift @@ -128,7 +128,139 @@ struct AllTypes { /// A class containing all supported nullable types. /// /// Generated class from Pigeon that represents data sent in messages. -struct AllNullableTypes { +class AllNullableTypes { + init( + aNullableBool: Bool? = nil, + aNullableInt: Int64? = nil, + aNullableInt64: Int64? = nil, + aNullableDouble: Double? = nil, + aNullableByteArray: FlutterStandardTypedData? = nil, + aNullable4ByteArray: FlutterStandardTypedData? = nil, + aNullable8ByteArray: FlutterStandardTypedData? = nil, + aNullableFloatArray: FlutterStandardTypedData? = nil, + aNullableList: [Any?]? = nil, + aNullableMap: [AnyHashable: Any?]? = nil, + nullableNestedList: [[Bool?]?]? = nil, + nullableMapWithAnnotations: [String?: String?]? = nil, + nullableMapWithObject: [String?: Any?]? = nil, + aNullableEnum: AnEnum? = nil, + aNullableString: String? = nil, + aNullableObject: Any? = nil, + allNullableTypes: AllNullableTypes? = nil + ) { + self.aNullableBool = aNullableBool + self.aNullableInt = aNullableInt + self.aNullableInt64 = aNullableInt64 + self.aNullableDouble = aNullableDouble + self.aNullableByteArray = aNullableByteArray + self.aNullable4ByteArray = aNullable4ByteArray + self.aNullable8ByteArray = aNullable8ByteArray + self.aNullableFloatArray = aNullableFloatArray + self.aNullableList = aNullableList + self.aNullableMap = aNullableMap + self.nullableNestedList = nullableNestedList + self.nullableMapWithAnnotations = nullableMapWithAnnotations + self.nullableMapWithObject = nullableMapWithObject + self.aNullableEnum = aNullableEnum + self.aNullableString = aNullableString + self.aNullableObject = aNullableObject + self.allNullableTypes = allNullableTypes + } + var aNullableBool: Bool? + var aNullableInt: Int64? + var aNullableInt64: Int64? + var aNullableDouble: Double? + var aNullableByteArray: FlutterStandardTypedData? + var aNullable4ByteArray: FlutterStandardTypedData? + var aNullable8ByteArray: FlutterStandardTypedData? + var aNullableFloatArray: FlutterStandardTypedData? + var aNullableList: [Any?]? + var aNullableMap: [AnyHashable: Any?]? + var nullableNestedList: [[Bool?]?]? + var nullableMapWithAnnotations: [String?: String?]? + var nullableMapWithObject: [String?: Any?]? + var aNullableEnum: AnEnum? + var aNullableString: String? + var aNullableObject: Any? + var allNullableTypes: AllNullableTypes? + + static func fromList(_ list: [Any?]) -> AllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(list[0]) + let aNullableInt: Int64? = + isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + let aNullableInt64: Int64? = + isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) + let aNullableList: [Any?]? = nilOrValue(list[8]) + let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) + var aNullableEnum: AnEnum? = nil + let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) + if let aNullableEnumRawValue = aNullableEnumEnumVal { + aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! + } + let aNullableString: String? = nilOrValue(list[14]) + let aNullableObject: Any? = list[15] + var allNullableTypes: AllNullableTypes? = nil + if let allNullableTypesList: [Any?] = nilOrValue(list[16]) { + allNullableTypes = AllNullableTypes.fromList(allNullableTypesList) + } + + return AllNullableTypes( + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableInt64: aNullableInt64, + aNullableDouble: aNullableDouble, + aNullableByteArray: aNullableByteArray, + aNullable4ByteArray: aNullable4ByteArray, + aNullable8ByteArray: aNullable8ByteArray, + aNullableFloatArray: aNullableFloatArray, + aNullableList: aNullableList, + aNullableMap: aNullableMap, + nullableNestedList: nullableNestedList, + nullableMapWithAnnotations: nullableMapWithAnnotations, + nullableMapWithObject: nullableMapWithObject, + aNullableEnum: aNullableEnum, + aNullableString: aNullableString, + aNullableObject: aNullableObject, + allNullableTypes: allNullableTypes + ) + } + func toList() -> [Any?] { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableList, + aNullableMap, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum?.rawValue, + aNullableString, + aNullableObject, + allNullableTypes?.toList(), + ] + } +} + +/// The primary purpose for this class is to ensure coverage of Swift structs +/// with nullable items, as the primary [AllNullableTypes] class is being used to +/// test Swift classes. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct AllNullableTypesWithoutRecursion { var aNullableBool: Bool? = nil var aNullableInt: Int64? = nil var aNullableInt64: Int64? = nil @@ -146,7 +278,7 @@ struct AllNullableTypes { var aNullableString: String? = nil var aNullableObject: Any? = nil - static func fromList(_ list: [Any?]) -> AllNullableTypes? { + static func fromList(_ list: [Any?]) -> AllNullableTypesWithoutRecursion? { let aNullableBool: Bool? = nilOrValue(list[0]) let aNullableInt: Int64? = isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) @@ -170,7 +302,7 @@ struct AllNullableTypes { let aNullableString: String? = nilOrValue(list[14]) let aNullableObject: Any? = list[15] - return AllNullableTypes( + return AllNullableTypesWithoutRecursion( aNullableBool: aNullableBool, aNullableInt: aNullableInt, aNullableInt64: aNullableInt64, @@ -220,23 +352,31 @@ struct AllNullableTypes { /// Generated class from Pigeon that represents data sent in messages. struct AllClassesWrapper { var allNullableTypes: AllNullableTypes + var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil var allTypes: AllTypes? = nil static func fromList(_ list: [Any?]) -> AllClassesWrapper? { let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])! + var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil + if let allNullableTypesWithoutRecursionList: [Any?] = nilOrValue(list[1]) { + allNullableTypesWithoutRecursion = AllNullableTypesWithoutRecursion.fromList( + allNullableTypesWithoutRecursionList) + } var allTypes: AllTypes? = nil - if let allTypesList: [Any?] = nilOrValue(list[1]) { + if let allTypesList: [Any?] = nilOrValue(list[2]) { allTypes = AllTypes.fromList(allTypesList) } return AllClassesWrapper( allNullableTypes: allNullableTypes, + allNullableTypesWithoutRecursion: allNullableTypesWithoutRecursion, allTypes: allTypes ) } func toList() -> [Any?] { return [ allNullableTypes.toList(), + allNullableTypesWithoutRecursion?.toList(), allTypes?.toList(), ] } @@ -270,8 +410,10 @@ private class HostIntegrationCoreApiCodecReader: FlutterStandardReader { case 129: return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 131: + return AllTypes.fromList(self.readValue() as! [Any?]) + case 132: return TestMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -287,12 +429,15 @@ private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter { } else if let value = value as? AllNullableTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllTypes { super.writeByte(131) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(132) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -358,6 +503,9 @@ protocol HostIntegrationCoreApi { func echoRequired(_ anInt: Int64) throws -> Int64 /// Returns the passed object, to test serialization and deserialization. func echo(_ everything: AllNullableTypes?) throws -> AllNullableTypes? + /// Returns the passed object, to test serialization and deserialization. + func echo(_ everything: AllNullableTypesWithoutRecursion?) throws + -> AllNullableTypesWithoutRecursion? /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. func extractNestedNullableString(from wrapper: AllClassesWrapper) throws -> String? @@ -368,6 +516,10 @@ protocol HostIntegrationCoreApi { func sendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? ) throws -> AllNullableTypes + /// Returns passed in arguments of multiple types. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? + ) throws -> AllNullableTypesWithoutRecursion /// Returns passed in int. func echo(_ aNullableInt: Int64?) throws -> Int64? /// Returns passed in double. @@ -425,6 +577,10 @@ protocol HostIntegrationCoreApi { func echoAsync( _ everything: AllNullableTypes?, completion: @escaping (Result) -> Void) + /// Returns the passed object, to test serialization and deserialization. + func echoAsync( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) /// Returns passed in int asynchronously. func echoAsyncNullable(_ anInt: Int64?, completion: @escaping (Result) -> Void) /// Returns passed in double asynchronously. @@ -457,6 +613,12 @@ protocol HostIntegrationCoreApi { func callFlutterSendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, completion: @escaping (Result) -> Void) + func callFlutterEcho( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) + func callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, + completion: @escaping (Result) -> Void) func callFlutterEcho(_ aBool: Bool, completion: @escaping (Result) -> Void) func callFlutterEcho(_ anInt: Int64, completion: @escaping (Result) -> Void) func callFlutterEcho(_ aDouble: Double, completion: @escaping (Result) -> Void) @@ -830,6 +992,25 @@ class HostIntegrationCoreApiSetup { } else { echoAllNullableTypesChannel.setMessageHandler(nil) } + /// Returns the passed object, to test serialization and deserialization. + let echoAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + echoAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + do { + let result = try api.echo(everythingArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + echoAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns the inner `aString` value from the wrapped object, to test /// sending of nested objects. let extractNestedNullableStringChannel = FlutterBasicMessageChannel( @@ -894,6 +1075,30 @@ class HostIntegrationCoreApiSetup { } else { sendMultipleNullableTypesChannel.setMessageHandler(nil) } + /// Returns passed in arguments of multiple types. + let sendMultipleNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + sendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let aNullableBoolArg: Bool? = nilOrValue(args[0]) + let aNullableIntArg: Int64? = + isNullish(args[1]) + ? nil : (args[1] is Int64? ? args[1] as! Int64? : Int64(args[1] as! Int32)) + let aNullableStringArg: String? = nilOrValue(args[2]) + do { + let result = try api.sendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBoolArg, anInt: aNullableIntArg, aString: aNullableStringArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + sendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns passed in int. let echoNullableIntChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoNullableInt", @@ -1394,6 +1599,27 @@ class HostIntegrationCoreApiSetup { } else { echoAsyncNullableAllNullableTypesChannel.setMessageHandler(nil) } + /// Returns the passed object, to test serialization and deserialization. + let echoAsyncNullableAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + echoAsyncNullableAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + api.echoAsync(everythingArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + echoAsyncNullableAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } /// Returns passed in int asynchronously. let echoAsyncNullableIntChannel = FlutterBasicMessageChannel( name: @@ -1704,6 +1930,53 @@ class HostIntegrationCoreApiSetup { } else { callFlutterSendMultipleNullableTypesChannel.setMessageHandler(nil) } + let callFlutterEchoAllNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoAllNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + callFlutterEchoAllNullableTypesWithoutRecursionChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let everythingArg: AllNullableTypesWithoutRecursion? = nilOrValue(args[0]) + api.callFlutterEcho(everythingArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + callFlutterEchoAllNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } + let callFlutterSendMultipleNullableTypesWithoutRecursionChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterSendMultipleNullableTypesWithoutRecursion", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + callFlutterSendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler { + message, reply in + let args = message as! [Any?] + let aNullableBoolArg: Bool? = nilOrValue(args[0]) + let aNullableIntArg: Int64? = + isNullish(args[1]) + ? nil : (args[1] is Int64? ? args[1] as! Int64? : Int64(args[1] as! Int32)) + let aNullableStringArg: String? = nilOrValue(args[2]) + api.callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBoolArg, anInt: aNullableIntArg, aString: aNullableStringArg + ) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + callFlutterSendMultipleNullableTypesWithoutRecursionChannel.setMessageHandler(nil) + } let callFlutterEchoBoolChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoBool", @@ -2034,8 +2307,10 @@ private class FlutterIntegrationCoreApiCodecReader: FlutterStandardReader { case 129: return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 131: + return AllTypes.fromList(self.readValue() as! [Any?]) + case 132: return TestMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -2051,12 +2326,15 @@ private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter { } else if let value = value as? AllNullableTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllTypes { super.writeByte(131) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(132) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -2104,6 +2382,17 @@ protocol FlutterIntegrationCoreApiProtocol { aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, completion: @escaping (Result) -> Void) + /// Returns the passed object, to test serialization and deserialization. + func echoNullable( + _ everythingArg: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void) + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, + aString aNullableStringArg: String?, + completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. @@ -2318,6 +2607,66 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } } + /// Returns the passed object, to test serialization and deserialization. + func echoNullable( + _ everythingArg: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([everythingArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(FlutterError(code: code, message: message, details: details))) + } else { + let result: AllNullableTypesWithoutRecursion? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + /// Returns passed in arguments of multiple types. + /// + /// Tests multiple-arity FlutterApi handling. + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, + aString aNullableStringArg: String?, + completion: @escaping (Result) -> Void + ) { + let channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([aNullableBoolArg, aNullableIntArg, aNullableStringArg] as [Any?]) { + response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(FlutterError(code: code, message: message, details: details))) + } else if listResponse[0] == nil { + completion( + .failure( + FlutterError( + code: "null-error", + message: "Flutter api returned null value for non-null return value.", details: ""))) + } else { + let result = listResponse[0] as! AllNullableTypesWithoutRecursion + completion(.success(result)) + } + } + } /// Returns the passed boolean, to test serialization and deserialization. func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { let channelName: String = diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift index 5da16290c7a0..019864394650 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift @@ -34,6 +34,11 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { func echo(_ everything: AllNullableTypes?) -> AllNullableTypes? { return everything } + func echo(_ everything: AllNullableTypesWithoutRecursion?) throws + -> AllNullableTypesWithoutRecursion? + { + return everything + } func throwError() throws -> Any? { throw FlutterError(code: "code", message: "message", details: "details") @@ -87,18 +92,6 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return anEnum } - func echoNamedDefault(_ aString: String) throws -> String { - return aString - } - - func echoOptionalDefault(_ aDouble: Double) throws -> Double { - return aDouble - } - - func echoRequired(_ anInt: Int64) throws -> Int64 { - return anInt - } - func extractNestedNullableString(from wrapper: AllClassesWrapper) -> String? { return wrapper.allNullableTypes.aNullableString } @@ -115,6 +108,14 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return someThings } + func sendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String? + ) throws -> AllNullableTypesWithoutRecursion { + let someThings = AllNullableTypesWithoutRecursion( + aNullableBool: aNullableBool, aNullableInt: aNullableInt, aNullableString: aNullableString) + return someThings + } + func echo(_ aNullableInt: Int64?) -> Int64? { return aNullableInt } @@ -139,6 +140,18 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return aNullableObject } + func echoNamedDefault(_ aString: String) throws -> String { + return aString + } + + func echoOptionalDefault(_ aDouble: Double) throws -> Double { + return aDouble + } + + func echoRequired(_ anInt: Int64) throws -> Int64 { + return anInt + } + func echoNullable(_ aNullableList: [Any?]?) throws -> [Any?]? { return aNullableList } @@ -186,6 +199,13 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(everything)) } + func echoAsync( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + completion(.success(everything)) + } + func echoAsync(_ anInt: Int64, completion: @escaping (Result) -> Void) { completion(.success(anInt)) } @@ -331,6 +351,20 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } + func callFlutterEcho( + _ everything: AllNullableTypesWithoutRecursion?, + completion: @escaping (Result) -> Void + ) { + flutterAPI.echoNullable(everything) { response in + switch response { + case .success(let res): + completion(.success(res)) + case .failure(let error): + completion(.failure(error)) + } + } + } + func callFlutterSendMultipleNullableTypes( aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, @@ -351,6 +385,24 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } + func callFlutterSendMultipleNullableTypesWithoutRecursion( + aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?, + completion: @escaping (Result) -> Void + ) { + flutterAPI.sendMultipleNullableTypesWithoutRecursion( + aBool: aNullableBool, + anInt: aNullableInt, + aString: aNullableString + ) { response in + switch response { + case .success(let res): + completion(.success(res)) + case .failure(let error): + completion(.failure(error)) + } + } + } + func callFlutterEcho(_ aBool: Bool, completion: @escaping (Result) -> Void) { flutterAPI.echo(aBool) { response in switch response { diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp index 2ca7340b0ceb..fd12459454a1 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp @@ -166,6 +166,542 @@ AllTypes AllTypes::FromEncodableList(const EncodableList& list) { AllNullableTypes::AllNullableTypes() {} AllNullableTypes::AllNullableTypes( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const int64_t* a_nullable_int64, const double* a_nullable_double, + const std::vector* a_nullable_byte_array, + const std::vector* a_nullable4_byte_array, + const std::vector* a_nullable8_byte_array, + const std::vector* a_nullable_float_array, + const EncodableList* a_nullable_list, const EncodableMap* a_nullable_map, + const EncodableList* nullable_nested_list, + const EncodableMap* nullable_map_with_annotations, + const EncodableMap* nullable_map_with_object, const AnEnum* a_nullable_enum, + const std::string* a_nullable_string, + const EncodableValue* a_nullable_object, + const AllNullableTypes* all_nullable_types) + : a_nullable_bool_(a_nullable_bool ? std::optional(*a_nullable_bool) + : std::nullopt), + a_nullable_int_(a_nullable_int ? std::optional(*a_nullable_int) + : std::nullopt), + a_nullable_int64_(a_nullable_int64 + ? std::optional(*a_nullable_int64) + : std::nullopt), + a_nullable_double_(a_nullable_double + ? std::optional(*a_nullable_double) + : std::nullopt), + a_nullable_byte_array_( + a_nullable_byte_array + ? std::optional>(*a_nullable_byte_array) + : std::nullopt), + a_nullable4_byte_array_( + a_nullable4_byte_array + ? std::optional>(*a_nullable4_byte_array) + : std::nullopt), + a_nullable8_byte_array_( + a_nullable8_byte_array + ? std::optional>(*a_nullable8_byte_array) + : std::nullopt), + a_nullable_float_array_( + a_nullable_float_array + ? std::optional>(*a_nullable_float_array) + : std::nullopt), + a_nullable_list_(a_nullable_list + ? std::optional(*a_nullable_list) + : std::nullopt), + a_nullable_map_(a_nullable_map + ? std::optional(*a_nullable_map) + : std::nullopt), + nullable_nested_list_(nullable_nested_list ? std::optional( + *nullable_nested_list) + : std::nullopt), + nullable_map_with_annotations_( + nullable_map_with_annotations + ? std::optional(*nullable_map_with_annotations) + : std::nullopt), + nullable_map_with_object_( + nullable_map_with_object + ? std::optional(*nullable_map_with_object) + : std::nullopt), + a_nullable_enum_(a_nullable_enum ? std::optional(*a_nullable_enum) + : std::nullopt), + a_nullable_string_(a_nullable_string + ? std::optional(*a_nullable_string) + : std::nullopt), + a_nullable_object_(a_nullable_object + ? std::optional(*a_nullable_object) + : std::nullopt), + all_nullable_types_( + all_nullable_types + ? std::make_unique(*all_nullable_types) + : nullptr) {} + +AllNullableTypes::AllNullableTypes(const AllNullableTypes& other) + : a_nullable_bool_(other.a_nullable_bool_ + ? std::optional(*other.a_nullable_bool_) + : std::nullopt), + a_nullable_int_(other.a_nullable_int_ + ? std::optional(*other.a_nullable_int_) + : std::nullopt), + a_nullable_int64_(other.a_nullable_int64_ + ? std::optional(*other.a_nullable_int64_) + : std::nullopt), + a_nullable_double_(other.a_nullable_double_ + ? std::optional(*other.a_nullable_double_) + : std::nullopt), + a_nullable_byte_array_(other.a_nullable_byte_array_ + ? std::optional>( + *other.a_nullable_byte_array_) + : std::nullopt), + a_nullable4_byte_array_(other.a_nullable4_byte_array_ + ? std::optional>( + *other.a_nullable4_byte_array_) + : std::nullopt), + a_nullable8_byte_array_(other.a_nullable8_byte_array_ + ? std::optional>( + *other.a_nullable8_byte_array_) + : std::nullopt), + a_nullable_float_array_(other.a_nullable_float_array_ + ? std::optional>( + *other.a_nullable_float_array_) + : std::nullopt), + a_nullable_list_(other.a_nullable_list_ ? std::optional( + *other.a_nullable_list_) + : std::nullopt), + a_nullable_map_(other.a_nullable_map_ + ? std::optional(*other.a_nullable_map_) + : std::nullopt), + nullable_nested_list_( + other.nullable_nested_list_ + ? std::optional(*other.nullable_nested_list_) + : std::nullopt), + nullable_map_with_annotations_( + other.nullable_map_with_annotations_ + ? std::optional( + *other.nullable_map_with_annotations_) + : std::nullopt), + nullable_map_with_object_( + other.nullable_map_with_object_ + ? std::optional(*other.nullable_map_with_object_) + : std::nullopt), + a_nullable_enum_(other.a_nullable_enum_ + ? std::optional(*other.a_nullable_enum_) + : std::nullopt), + a_nullable_string_( + other.a_nullable_string_ + ? std::optional(*other.a_nullable_string_) + : std::nullopt), + a_nullable_object_( + other.a_nullable_object_ + ? std::optional(*other.a_nullable_object_) + : std::nullopt), + all_nullable_types_( + other.all_nullable_types_ + ? std::make_unique(*other.all_nullable_types_) + : nullptr) {} + +AllNullableTypes& AllNullableTypes::operator=(const AllNullableTypes& other) { + a_nullable_bool_ = other.a_nullable_bool_; + a_nullable_int_ = other.a_nullable_int_; + a_nullable_int64_ = other.a_nullable_int64_; + a_nullable_double_ = other.a_nullable_double_; + a_nullable_byte_array_ = other.a_nullable_byte_array_; + a_nullable4_byte_array_ = other.a_nullable4_byte_array_; + a_nullable8_byte_array_ = other.a_nullable8_byte_array_; + a_nullable_float_array_ = other.a_nullable_float_array_; + a_nullable_list_ = other.a_nullable_list_; + a_nullable_map_ = other.a_nullable_map_; + nullable_nested_list_ = other.nullable_nested_list_; + nullable_map_with_annotations_ = other.nullable_map_with_annotations_; + nullable_map_with_object_ = other.nullable_map_with_object_; + a_nullable_enum_ = other.a_nullable_enum_; + a_nullable_string_ = other.a_nullable_string_; + a_nullable_object_ = other.a_nullable_object_; + all_nullable_types_ = + other.all_nullable_types_ + ? std::make_unique(*other.all_nullable_types_) + : nullptr; + return *this; +} + +const bool* AllNullableTypes::a_nullable_bool() const { + return a_nullable_bool_ ? &(*a_nullable_bool_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_bool(const bool* value_arg) { + a_nullable_bool_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_bool(bool value_arg) { + a_nullable_bool_ = value_arg; +} + +const int64_t* AllNullableTypes::a_nullable_int() const { + return a_nullable_int_ ? &(*a_nullable_int_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_int(const int64_t* value_arg) { + a_nullable_int_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_int(int64_t value_arg) { + a_nullable_int_ = value_arg; +} + +const int64_t* AllNullableTypes::a_nullable_int64() const { + return a_nullable_int64_ ? &(*a_nullable_int64_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_int64(const int64_t* value_arg) { + a_nullable_int64_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_int64(int64_t value_arg) { + a_nullable_int64_ = value_arg; +} + +const double* AllNullableTypes::a_nullable_double() const { + return a_nullable_double_ ? &(*a_nullable_double_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_double(const double* value_arg) { + a_nullable_double_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_double(double value_arg) { + a_nullable_double_ = value_arg; +} + +const std::vector* AllNullableTypes::a_nullable_byte_array() const { + return a_nullable_byte_array_ ? &(*a_nullable_byte_array_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_byte_array( + const std::vector* value_arg) { + a_nullable_byte_array_ = value_arg + ? std::optional>(*value_arg) + : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_byte_array( + const std::vector& value_arg) { + a_nullable_byte_array_ = value_arg; +} + +const std::vector* AllNullableTypes::a_nullable4_byte_array() const { + return a_nullable4_byte_array_ ? &(*a_nullable4_byte_array_) : nullptr; +} + +void AllNullableTypes::set_a_nullable4_byte_array( + const std::vector* value_arg) { + a_nullable4_byte_array_ = + value_arg ? std::optional>(*value_arg) + : std::nullopt; +} + +void AllNullableTypes::set_a_nullable4_byte_array( + const std::vector& value_arg) { + a_nullable4_byte_array_ = value_arg; +} + +const std::vector* AllNullableTypes::a_nullable8_byte_array() const { + return a_nullable8_byte_array_ ? &(*a_nullable8_byte_array_) : nullptr; +} + +void AllNullableTypes::set_a_nullable8_byte_array( + const std::vector* value_arg) { + a_nullable8_byte_array_ = + value_arg ? std::optional>(*value_arg) + : std::nullopt; +} + +void AllNullableTypes::set_a_nullable8_byte_array( + const std::vector& value_arg) { + a_nullable8_byte_array_ = value_arg; +} + +const std::vector* AllNullableTypes::a_nullable_float_array() const { + return a_nullable_float_array_ ? &(*a_nullable_float_array_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_float_array( + const std::vector* value_arg) { + a_nullable_float_array_ = + value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_float_array( + const std::vector& value_arg) { + a_nullable_float_array_ = value_arg; +} + +const EncodableList* AllNullableTypes::a_nullable_list() const { + return a_nullable_list_ ? &(*a_nullable_list_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_list(const EncodableList* value_arg) { + a_nullable_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_list(const EncodableList& value_arg) { + a_nullable_list_ = value_arg; +} + +const EncodableMap* AllNullableTypes::a_nullable_map() const { + return a_nullable_map_ ? &(*a_nullable_map_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_map(const EncodableMap* value_arg) { + a_nullable_map_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_map(const EncodableMap& value_arg) { + a_nullable_map_ = value_arg; +} + +const EncodableList* AllNullableTypes::nullable_nested_list() const { + return nullable_nested_list_ ? &(*nullable_nested_list_) : nullptr; +} + +void AllNullableTypes::set_nullable_nested_list( + const EncodableList* value_arg) { + nullable_nested_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_nullable_nested_list( + const EncodableList& value_arg) { + nullable_nested_list_ = value_arg; +} + +const EncodableMap* AllNullableTypes::nullable_map_with_annotations() const { + return nullable_map_with_annotations_ ? &(*nullable_map_with_annotations_) + : nullptr; +} + +void AllNullableTypes::set_nullable_map_with_annotations( + const EncodableMap* value_arg) { + nullable_map_with_annotations_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_nullable_map_with_annotations( + const EncodableMap& value_arg) { + nullable_map_with_annotations_ = value_arg; +} + +const EncodableMap* AllNullableTypes::nullable_map_with_object() const { + return nullable_map_with_object_ ? &(*nullable_map_with_object_) : nullptr; +} + +void AllNullableTypes::set_nullable_map_with_object( + const EncodableMap* value_arg) { + nullable_map_with_object_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_nullable_map_with_object( + const EncodableMap& value_arg) { + nullable_map_with_object_ = value_arg; +} + +const AnEnum* AllNullableTypes::a_nullable_enum() const { + return a_nullable_enum_ ? &(*a_nullable_enum_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_enum(const AnEnum* value_arg) { + a_nullable_enum_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_enum(const AnEnum& value_arg) { + a_nullable_enum_ = value_arg; +} + +const std::string* AllNullableTypes::a_nullable_string() const { + return a_nullable_string_ ? &(*a_nullable_string_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_string( + const std::string_view* value_arg) { + a_nullable_string_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_string(std::string_view value_arg) { + a_nullable_string_ = value_arg; +} + +const EncodableValue* AllNullableTypes::a_nullable_object() const { + return a_nullable_object_ ? &(*a_nullable_object_) : nullptr; +} + +void AllNullableTypes::set_a_nullable_object(const EncodableValue* value_arg) { + a_nullable_object_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_a_nullable_object(const EncodableValue& value_arg) { + a_nullable_object_ = value_arg; +} + +const AllNullableTypes* AllNullableTypes::all_nullable_types() const { + return all_nullable_types_.get(); +} + +void AllNullableTypes::set_all_nullable_types( + const AllNullableTypes* value_arg) { + all_nullable_types_ = + value_arg ? std::make_unique(*value_arg) : nullptr; +} + +void AllNullableTypes::set_all_nullable_types( + const AllNullableTypes& value_arg) { + all_nullable_types_ = std::make_unique(value_arg); +} + +EncodableList AllNullableTypes::ToEncodableList() const { + EncodableList list; + list.reserve(17); + list.push_back(a_nullable_bool_ ? EncodableValue(*a_nullable_bool_) + : EncodableValue()); + list.push_back(a_nullable_int_ ? EncodableValue(*a_nullable_int_) + : EncodableValue()); + list.push_back(a_nullable_int64_ ? EncodableValue(*a_nullable_int64_) + : EncodableValue()); + list.push_back(a_nullable_double_ ? EncodableValue(*a_nullable_double_) + : EncodableValue()); + list.push_back(a_nullable_byte_array_ + ? EncodableValue(*a_nullable_byte_array_) + : EncodableValue()); + list.push_back(a_nullable4_byte_array_ + ? EncodableValue(*a_nullable4_byte_array_) + : EncodableValue()); + list.push_back(a_nullable8_byte_array_ + ? EncodableValue(*a_nullable8_byte_array_) + : EncodableValue()); + list.push_back(a_nullable_float_array_ + ? EncodableValue(*a_nullable_float_array_) + : EncodableValue()); + list.push_back(a_nullable_list_ ? EncodableValue(*a_nullable_list_) + : EncodableValue()); + list.push_back(a_nullable_map_ ? EncodableValue(*a_nullable_map_) + : EncodableValue()); + list.push_back(nullable_nested_list_ ? EncodableValue(*nullable_nested_list_) + : EncodableValue()); + list.push_back(nullable_map_with_annotations_ + ? EncodableValue(*nullable_map_with_annotations_) + : EncodableValue()); + list.push_back(nullable_map_with_object_ + ? EncodableValue(*nullable_map_with_object_) + : EncodableValue()); + list.push_back(a_nullable_enum_ ? EncodableValue((int)(*a_nullable_enum_)) + : EncodableValue()); + list.push_back(a_nullable_string_ ? EncodableValue(*a_nullable_string_) + : EncodableValue()); + list.push_back(a_nullable_object_ ? *a_nullable_object_ : EncodableValue()); + list.push_back(all_nullable_types_ + ? EncodableValue(all_nullable_types_->ToEncodableList()) + : EncodableValue()); + return list; +} + +AllNullableTypes AllNullableTypes::FromEncodableList( + const EncodableList& list) { + AllNullableTypes decoded; + auto& encodable_a_nullable_bool = list[0]; + if (!encodable_a_nullable_bool.IsNull()) { + decoded.set_a_nullable_bool(std::get(encodable_a_nullable_bool)); + } + auto& encodable_a_nullable_int = list[1]; + if (!encodable_a_nullable_int.IsNull()) { + decoded.set_a_nullable_int(encodable_a_nullable_int.LongValue()); + } + auto& encodable_a_nullable_int64 = list[2]; + if (!encodable_a_nullable_int64.IsNull()) { + decoded.set_a_nullable_int64(encodable_a_nullable_int64.LongValue()); + } + auto& encodable_a_nullable_double = list[3]; + if (!encodable_a_nullable_double.IsNull()) { + decoded.set_a_nullable_double( + std::get(encodable_a_nullable_double)); + } + auto& encodable_a_nullable_byte_array = list[4]; + if (!encodable_a_nullable_byte_array.IsNull()) { + decoded.set_a_nullable_byte_array( + std::get>(encodable_a_nullable_byte_array)); + } + auto& encodable_a_nullable4_byte_array = list[5]; + if (!encodable_a_nullable4_byte_array.IsNull()) { + decoded.set_a_nullable4_byte_array( + std::get>(encodable_a_nullable4_byte_array)); + } + auto& encodable_a_nullable8_byte_array = list[6]; + if (!encodable_a_nullable8_byte_array.IsNull()) { + decoded.set_a_nullable8_byte_array( + std::get>(encodable_a_nullable8_byte_array)); + } + auto& encodable_a_nullable_float_array = list[7]; + if (!encodable_a_nullable_float_array.IsNull()) { + decoded.set_a_nullable_float_array( + std::get>(encodable_a_nullable_float_array)); + } + auto& encodable_a_nullable_list = list[8]; + if (!encodable_a_nullable_list.IsNull()) { + decoded.set_a_nullable_list( + std::get(encodable_a_nullable_list)); + } + auto& encodable_a_nullable_map = list[9]; + if (!encodable_a_nullable_map.IsNull()) { + decoded.set_a_nullable_map( + std::get(encodable_a_nullable_map)); + } + auto& encodable_nullable_nested_list = list[10]; + if (!encodable_nullable_nested_list.IsNull()) { + decoded.set_nullable_nested_list( + std::get(encodable_nullable_nested_list)); + } + auto& encodable_nullable_map_with_annotations = list[11]; + if (!encodable_nullable_map_with_annotations.IsNull()) { + decoded.set_nullable_map_with_annotations( + std::get(encodable_nullable_map_with_annotations)); + } + auto& encodable_nullable_map_with_object = list[12]; + if (!encodable_nullable_map_with_object.IsNull()) { + decoded.set_nullable_map_with_object( + std::get(encodable_nullable_map_with_object)); + } + auto& encodable_a_nullable_enum = list[13]; + if (!encodable_a_nullable_enum.IsNull()) { + decoded.set_a_nullable_enum( + (AnEnum)(std::get(encodable_a_nullable_enum))); + } + auto& encodable_a_nullable_string = list[14]; + if (!encodable_a_nullable_string.IsNull()) { + decoded.set_a_nullable_string( + std::get(encodable_a_nullable_string)); + } + auto& encodable_a_nullable_object = list[15]; + if (!encodable_a_nullable_object.IsNull()) { + decoded.set_a_nullable_object(encodable_a_nullable_object); + } + auto& encodable_all_nullable_types = list[16]; + if (!encodable_all_nullable_types.IsNull()) { + decoded.set_all_nullable_types(AllNullableTypes::FromEncodableList( + std::get(encodable_all_nullable_types))); + } + return decoded; +} + +// AllNullableTypesWithoutRecursion + +AllNullableTypesWithoutRecursion::AllNullableTypesWithoutRecursion() {} + +AllNullableTypesWithoutRecursion::AllNullableTypesWithoutRecursion( const bool* a_nullable_bool, const int64_t* a_nullable_int, const int64_t* a_nullable_int64, const double* a_nullable_double, const std::vector* a_nullable_byte_array, @@ -230,233 +766,254 @@ AllNullableTypes::AllNullableTypes( ? std::optional(*a_nullable_object) : std::nullopt) {} -const bool* AllNullableTypes::a_nullable_bool() const { +const bool* AllNullableTypesWithoutRecursion::a_nullable_bool() const { return a_nullable_bool_ ? &(*a_nullable_bool_) : nullptr; } -void AllNullableTypes::set_a_nullable_bool(const bool* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_bool( + const bool* value_arg) { a_nullable_bool_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_bool(bool value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_bool(bool value_arg) { a_nullable_bool_ = value_arg; } -const int64_t* AllNullableTypes::a_nullable_int() const { +const int64_t* AllNullableTypesWithoutRecursion::a_nullable_int() const { return a_nullable_int_ ? &(*a_nullable_int_) : nullptr; } -void AllNullableTypes::set_a_nullable_int(const int64_t* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_int( + const int64_t* value_arg) { a_nullable_int_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_int(int64_t value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_int(int64_t value_arg) { a_nullable_int_ = value_arg; } -const int64_t* AllNullableTypes::a_nullable_int64() const { +const int64_t* AllNullableTypesWithoutRecursion::a_nullable_int64() const { return a_nullable_int64_ ? &(*a_nullable_int64_) : nullptr; } -void AllNullableTypes::set_a_nullable_int64(const int64_t* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_int64( + const int64_t* value_arg) { a_nullable_int64_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_int64(int64_t value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_int64(int64_t value_arg) { a_nullable_int64_ = value_arg; } -const double* AllNullableTypes::a_nullable_double() const { +const double* AllNullableTypesWithoutRecursion::a_nullable_double() const { return a_nullable_double_ ? &(*a_nullable_double_) : nullptr; } -void AllNullableTypes::set_a_nullable_double(const double* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_double( + const double* value_arg) { a_nullable_double_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_double(double value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_double(double value_arg) { a_nullable_double_ = value_arg; } -const std::vector* AllNullableTypes::a_nullable_byte_array() const { +const std::vector* +AllNullableTypesWithoutRecursion::a_nullable_byte_array() const { return a_nullable_byte_array_ ? &(*a_nullable_byte_array_) : nullptr; } -void AllNullableTypes::set_a_nullable_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable_byte_array( const std::vector* value_arg) { a_nullable_byte_array_ = value_arg ? std::optional>(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable_byte_array( const std::vector& value_arg) { a_nullable_byte_array_ = value_arg; } -const std::vector* AllNullableTypes::a_nullable4_byte_array() const { +const std::vector* +AllNullableTypesWithoutRecursion::a_nullable4_byte_array() const { return a_nullable4_byte_array_ ? &(*a_nullable4_byte_array_) : nullptr; } -void AllNullableTypes::set_a_nullable4_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable4_byte_array( const std::vector* value_arg) { a_nullable4_byte_array_ = value_arg ? std::optional>(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable4_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable4_byte_array( const std::vector& value_arg) { a_nullable4_byte_array_ = value_arg; } -const std::vector* AllNullableTypes::a_nullable8_byte_array() const { +const std::vector* +AllNullableTypesWithoutRecursion::a_nullable8_byte_array() const { return a_nullable8_byte_array_ ? &(*a_nullable8_byte_array_) : nullptr; } -void AllNullableTypes::set_a_nullable8_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable8_byte_array( const std::vector* value_arg) { a_nullable8_byte_array_ = value_arg ? std::optional>(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable8_byte_array( +void AllNullableTypesWithoutRecursion::set_a_nullable8_byte_array( const std::vector& value_arg) { a_nullable8_byte_array_ = value_arg; } -const std::vector* AllNullableTypes::a_nullable_float_array() const { +const std::vector* +AllNullableTypesWithoutRecursion::a_nullable_float_array() const { return a_nullable_float_array_ ? &(*a_nullable_float_array_) : nullptr; } -void AllNullableTypes::set_a_nullable_float_array( +void AllNullableTypesWithoutRecursion::set_a_nullable_float_array( const std::vector* value_arg) { a_nullable_float_array_ = value_arg ? std::optional>(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_float_array( +void AllNullableTypesWithoutRecursion::set_a_nullable_float_array( const std::vector& value_arg) { a_nullable_float_array_ = value_arg; } -const EncodableList* AllNullableTypes::a_nullable_list() const { +const EncodableList* AllNullableTypesWithoutRecursion::a_nullable_list() const { return a_nullable_list_ ? &(*a_nullable_list_) : nullptr; } -void AllNullableTypes::set_a_nullable_list(const EncodableList* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_list( + const EncodableList* value_arg) { a_nullable_list_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_list(const EncodableList& value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_list( + const EncodableList& value_arg) { a_nullable_list_ = value_arg; } -const EncodableMap* AllNullableTypes::a_nullable_map() const { +const EncodableMap* AllNullableTypesWithoutRecursion::a_nullable_map() const { return a_nullable_map_ ? &(*a_nullable_map_) : nullptr; } -void AllNullableTypes::set_a_nullable_map(const EncodableMap* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_map( + const EncodableMap* value_arg) { a_nullable_map_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_map(const EncodableMap& value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_map( + const EncodableMap& value_arg) { a_nullable_map_ = value_arg; } -const EncodableList* AllNullableTypes::nullable_nested_list() const { +const EncodableList* AllNullableTypesWithoutRecursion::nullable_nested_list() + const { return nullable_nested_list_ ? &(*nullable_nested_list_) : nullptr; } -void AllNullableTypes::set_nullable_nested_list( +void AllNullableTypesWithoutRecursion::set_nullable_nested_list( const EncodableList* value_arg) { nullable_nested_list_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_nullable_nested_list( +void AllNullableTypesWithoutRecursion::set_nullable_nested_list( const EncodableList& value_arg) { nullable_nested_list_ = value_arg; } -const EncodableMap* AllNullableTypes::nullable_map_with_annotations() const { +const EncodableMap* +AllNullableTypesWithoutRecursion::nullable_map_with_annotations() const { return nullable_map_with_annotations_ ? &(*nullable_map_with_annotations_) : nullptr; } -void AllNullableTypes::set_nullable_map_with_annotations( +void AllNullableTypesWithoutRecursion::set_nullable_map_with_annotations( const EncodableMap* value_arg) { nullable_map_with_annotations_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_nullable_map_with_annotations( +void AllNullableTypesWithoutRecursion::set_nullable_map_with_annotations( const EncodableMap& value_arg) { nullable_map_with_annotations_ = value_arg; } -const EncodableMap* AllNullableTypes::nullable_map_with_object() const { +const EncodableMap* AllNullableTypesWithoutRecursion::nullable_map_with_object() + const { return nullable_map_with_object_ ? &(*nullable_map_with_object_) : nullptr; } -void AllNullableTypes::set_nullable_map_with_object( +void AllNullableTypesWithoutRecursion::set_nullable_map_with_object( const EncodableMap* value_arg) { nullable_map_with_object_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_nullable_map_with_object( +void AllNullableTypesWithoutRecursion::set_nullable_map_with_object( const EncodableMap& value_arg) { nullable_map_with_object_ = value_arg; } -const AnEnum* AllNullableTypes::a_nullable_enum() const { +const AnEnum* AllNullableTypesWithoutRecursion::a_nullable_enum() const { return a_nullable_enum_ ? &(*a_nullable_enum_) : nullptr; } -void AllNullableTypes::set_a_nullable_enum(const AnEnum* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_enum( + const AnEnum* value_arg) { a_nullable_enum_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_enum(const AnEnum& value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_enum( + const AnEnum& value_arg) { a_nullable_enum_ = value_arg; } -const std::string* AllNullableTypes::a_nullable_string() const { +const std::string* AllNullableTypesWithoutRecursion::a_nullable_string() const { return a_nullable_string_ ? &(*a_nullable_string_) : nullptr; } -void AllNullableTypes::set_a_nullable_string( +void AllNullableTypesWithoutRecursion::set_a_nullable_string( const std::string_view* value_arg) { a_nullable_string_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_string(std::string_view value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_string( + std::string_view value_arg) { a_nullable_string_ = value_arg; } -const EncodableValue* AllNullableTypes::a_nullable_object() const { +const EncodableValue* AllNullableTypesWithoutRecursion::a_nullable_object() + const { return a_nullable_object_ ? &(*a_nullable_object_) : nullptr; } -void AllNullableTypes::set_a_nullable_object(const EncodableValue* value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_object( + const EncodableValue* value_arg) { a_nullable_object_ = value_arg ? std::optional(*value_arg) : std::nullopt; } -void AllNullableTypes::set_a_nullable_object(const EncodableValue& value_arg) { +void AllNullableTypesWithoutRecursion::set_a_nullable_object( + const EncodableValue& value_arg) { a_nullable_object_ = value_arg; } -EncodableList AllNullableTypes::ToEncodableList() const { +EncodableList AllNullableTypesWithoutRecursion::ToEncodableList() const { EncodableList list; list.reserve(16); list.push_back(a_nullable_bool_ ? EncodableValue(*a_nullable_bool_) @@ -499,9 +1056,9 @@ EncodableList AllNullableTypes::ToEncodableList() const { return list; } -AllNullableTypes AllNullableTypes::FromEncodableList( - const EncodableList& list) { - AllNullableTypes decoded; +AllNullableTypesWithoutRecursion +AllNullableTypesWithoutRecursion::FromEncodableList(const EncodableList& list) { + AllNullableTypesWithoutRecursion decoded; auto& encodable_a_nullable_bool = list[0]; if (!encodable_a_nullable_bool.IsNull()) { decoded.set_a_nullable_bool(std::get(encodable_a_nullable_bool)); @@ -584,39 +1141,97 @@ AllNullableTypes AllNullableTypes::FromEncodableList( // AllClassesWrapper AllClassesWrapper::AllClassesWrapper(const AllNullableTypes& all_nullable_types) - : all_nullable_types_(all_nullable_types) {} + : all_nullable_types_( + std::make_unique(all_nullable_types)) {} AllClassesWrapper::AllClassesWrapper(const AllNullableTypes& all_nullable_types, + const AllNullableTypesWithoutRecursion* + all_nullable_types_without_recursion, const AllTypes* all_types) - : all_nullable_types_(all_nullable_types), - all_types_(all_types ? std::optional(*all_types) - : std::nullopt) {} + : all_nullable_types_( + std::make_unique(all_nullable_types)), + all_nullable_types_without_recursion_( + all_nullable_types_without_recursion + ? std::make_unique( + *all_nullable_types_without_recursion) + : nullptr), + all_types_(all_types ? std::make_unique(*all_types) : nullptr) { +} + +AllClassesWrapper::AllClassesWrapper(const AllClassesWrapper& other) + : all_nullable_types_( + std::make_unique(*other.all_nullable_types_)), + all_nullable_types_without_recursion_( + other.all_nullable_types_without_recursion_ + ? std::make_unique( + *other.all_nullable_types_without_recursion_) + : nullptr), + all_types_(other.all_types_ + ? std::make_unique(*other.all_types_) + : nullptr) {} + +AllClassesWrapper& AllClassesWrapper::operator=( + const AllClassesWrapper& other) { + all_nullable_types_ = + std::make_unique(*other.all_nullable_types_); + all_nullable_types_without_recursion_ = + other.all_nullable_types_without_recursion_ + ? std::make_unique( + *other.all_nullable_types_without_recursion_) + : nullptr; + all_types_ = other.all_types_ ? std::make_unique(*other.all_types_) + : nullptr; + return *this; +} const AllNullableTypes& AllClassesWrapper::all_nullable_types() const { - return all_nullable_types_; + return *all_nullable_types_; } void AllClassesWrapper::set_all_nullable_types( const AllNullableTypes& value_arg) { - all_nullable_types_ = value_arg; + all_nullable_types_ = std::make_unique(value_arg); +} + +const AllNullableTypesWithoutRecursion* +AllClassesWrapper::all_nullable_types_without_recursion() const { + return all_nullable_types_without_recursion_.get(); +} + +void AllClassesWrapper::set_all_nullable_types_without_recursion( + const AllNullableTypesWithoutRecursion* value_arg) { + all_nullable_types_without_recursion_ = + value_arg ? std::make_unique(*value_arg) + : nullptr; +} + +void AllClassesWrapper::set_all_nullable_types_without_recursion( + const AllNullableTypesWithoutRecursion& value_arg) { + all_nullable_types_without_recursion_ = + std::make_unique(value_arg); } const AllTypes* AllClassesWrapper::all_types() const { - return all_types_ ? &(*all_types_) : nullptr; + return all_types_.get(); } void AllClassesWrapper::set_all_types(const AllTypes* value_arg) { - all_types_ = value_arg ? std::optional(*value_arg) : std::nullopt; + all_types_ = value_arg ? std::make_unique(*value_arg) : nullptr; } void AllClassesWrapper::set_all_types(const AllTypes& value_arg) { - all_types_ = value_arg; + all_types_ = std::make_unique(value_arg); } EncodableList AllClassesWrapper::ToEncodableList() const { EncodableList list; - list.reserve(2); - list.push_back(EncodableValue(all_nullable_types_.ToEncodableList())); + list.reserve(3); + list.push_back(EncodableValue(all_nullable_types_->ToEncodableList())); + list.push_back( + all_nullable_types_without_recursion_ + ? EncodableValue( + all_nullable_types_without_recursion_->ToEncodableList()) + : EncodableValue()); list.push_back(all_types_ ? EncodableValue(all_types_->ToEncodableList()) : EncodableValue()); return list; @@ -626,7 +1241,14 @@ AllClassesWrapper AllClassesWrapper::FromEncodableList( const EncodableList& list) { AllClassesWrapper decoded( AllNullableTypes::FromEncodableList(std::get(list[0]))); - auto& encodable_all_types = list[1]; + auto& encodable_all_nullable_types_without_recursion = list[1]; + if (!encodable_all_nullable_types_without_recursion.IsNull()) { + decoded.set_all_nullable_types_without_recursion( + AllNullableTypesWithoutRecursion::FromEncodableList( + std::get( + encodable_all_nullable_types_without_recursion))); + } + auto& encodable_all_types = list[2]; if (!encodable_all_types.IsNull()) { decoded.set_all_types(AllTypes::FromEncodableList( std::get(encodable_all_types))); @@ -684,9 +1306,13 @@ EncodableValue HostIntegrationCoreApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(AllNullableTypes::FromEncodableList( std::get(ReadValue(stream)))); case 130: + return CustomEncodableValue( + AllNullableTypesWithoutRecursion::FromEncodableList( + std::get(ReadValue(stream)))); + case 131: return CustomEncodableValue(AllTypes::FromEncodableList( std::get(ReadValue(stream)))); - case 131: + case 132: return CustomEncodableValue(TestMessage::FromEncodableList( std::get(ReadValue(stream)))); default: @@ -713,15 +1339,23 @@ void HostIntegrationCoreApiCodecSerializer::WriteValue( stream); return; } - if (custom_value->type() == typeid(AllTypes)) { + if (custom_value->type() == typeid(AllNullableTypesWithoutRecursion)) { stream->WriteByte(130); + WriteValue(EncodableValue(std::any_cast( + *custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(AllTypes)) { + stream->WriteByte(131); WriteValue(EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(TestMessage)) { - stream->WriteByte(131); + stream->WriteByte(132); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -743,13 +1377,12 @@ const flutter::StandardMessageCodec& HostIntegrationCoreApi::GetCodec() { void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, HostIntegrationCoreApi* api) { { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "noop", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.noop", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -766,17 +1399,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAllTypes", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAllTypes", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -802,17 +1434,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "throwError", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.throwError", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -835,17 +1466,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "throwErrorFromVoid", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.throwErrorFromVoid", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -862,17 +1492,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "throwFlutterError", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.throwFlutterError", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -896,17 +1525,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoInt", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoInt", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -930,17 +1558,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoDouble", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoDouble", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -965,17 +1592,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoBool", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoBool", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -999,17 +1625,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoString", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoString", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1034,17 +1659,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoUint8List", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoUint8List", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1070,17 +1694,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoObject", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoObject", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1104,17 +1727,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoList", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoList", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1139,17 +1761,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoMap", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoMap", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1174,17 +1795,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoClassWrapper", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoClassWrapper", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1211,17 +1831,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoEnum", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoEnum", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1247,17 +1866,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoNamedDefaultString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1283,17 +1902,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoOptionalDefaultDouble", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1319,17 +1938,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoRequiredInt", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoRequiredInt", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1353,17 +1971,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAllNullableTypes", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAllNullableTypes", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1393,17 +2010,57 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + "echoAllNullableTypesWithoutRecursion", + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + &(std::any_cast( + std::get( + encodable_everything_arg))); + ErrorOr> output = + api->EchoAllNullableTypesWithoutRecursion(everything_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + CustomEncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "extractNestedNullableString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1435,17 +2092,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "createNestedNullableString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1468,17 +2125,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "sendMultipleNullableTypes", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1514,17 +2171,63 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableInt", + "sendMultipleNullableTypesWithoutRecursion", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_a_nullable_bool_arg = args.at(0); + const auto* a_nullable_bool_arg = + std::get_if(&encodable_a_nullable_bool_arg); + const auto& encodable_a_nullable_int_arg = args.at(1); + const int64_t a_nullable_int_arg_value = + encodable_a_nullable_int_arg.IsNull() + ? 0 + : encodable_a_nullable_int_arg.LongValue(); + const auto* a_nullable_int_arg = + encodable_a_nullable_int_arg.IsNull() + ? nullptr + : &a_nullable_int_arg_value; + const auto& encodable_a_nullable_string_arg = args.at(2); + const auto* a_nullable_string_arg = + std::get_if(&encodable_a_nullable_string_arg); + ErrorOr output = + api->SendMultipleNullableTypesWithoutRecursion( + a_nullable_bool_arg, a_nullable_int_arg, + a_nullable_string_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableInt", + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1558,17 +2261,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableDouble", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableDouble", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1596,17 +2298,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } - } - { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableBool", - &GetCodec()); + } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableBool", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1634,17 +2335,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableString", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableString", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1672,17 +2372,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoNullableUint8List", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1711,17 +2411,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableObject", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableObject", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1749,17 +2448,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableList", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableList", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1787,17 +2485,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableMap", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableMap", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1825,17 +2522,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoNullableEnum", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoNullableEnum", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1870,17 +2566,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoOptionalNullableInt", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1914,17 +2610,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoNamedNullableString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1952,17 +2648,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "noopAsync", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.noopAsync", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -1980,17 +2675,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncInt", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncInt", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2016,17 +2710,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncDouble", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncDouble", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2054,17 +2747,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncBool", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncBool", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2090,17 +2782,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncString", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncString", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2128,17 +2819,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncUint8List", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncUint8List", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2167,17 +2857,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncObject", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncObject", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2204,17 +2893,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncList", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncList", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2242,17 +2930,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncMap", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncMap", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2280,17 +2967,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncEnum", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncEnum", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2318,17 +3004,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "throwAsyncError", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.throwAsyncError", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2353,17 +3038,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "throwAsyncErrorFromVoid", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2382,17 +3067,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "throwAsyncFlutterError", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2417,17 +3102,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncAllTypes", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncAllTypes", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2455,17 +3139,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableAllNullableTypes", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2497,17 +3181,60 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncNullableInt", + "echoAsyncNullableAllNullableTypesWithoutRecursion", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + &(std::any_cast( + std::get( + encodable_everything_arg))); + api->EchoAsyncNullableAllNullableTypesWithoutRecursion( + everything_arg, + [reply]( + ErrorOr>&& + output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back(CustomEncodableValue( + std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncNullableInt", + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2541,17 +3268,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableDouble", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2581,17 +3308,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableBool", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2619,17 +3346,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2659,17 +3386,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableUint8List", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2700,17 +3427,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableObject", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2739,17 +3466,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableList", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2779,17 +3506,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "echoAsyncNullableMap", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.echoAsyncNullableMap", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2819,17 +3545,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "echoAsyncNullableEnum", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2866,17 +3592,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterNoop", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterNoop", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2895,17 +3620,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterThrowError", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2930,17 +3655,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterThrowErrorFromVoid", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2959,17 +3684,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoAllTypes", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -2997,17 +3722,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoAllNullableTypes", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3039,17 +3764,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterSendMultipleNullableTypes", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3087,17 +3812,108 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." + "callFlutterEchoAllNullableTypesWithoutRecursion", + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + &(std::any_cast( + std::get( + encodable_everything_arg))); + api->CallFlutterEchoAllNullableTypesWithoutRecursion( + everything_arg, + [reply]( + ErrorOr>&& + output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back(CustomEncodableValue( + std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterEchoBool", + "callFlutterSendMultipleNullableTypesWithoutRecursion", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_a_nullable_bool_arg = args.at(0); + const auto* a_nullable_bool_arg = + std::get_if(&encodable_a_nullable_bool_arg); + const auto& encodable_a_nullable_int_arg = args.at(1); + const int64_t a_nullable_int_arg_value = + encodable_a_nullable_int_arg.IsNull() + ? 0 + : encodable_a_nullable_int_arg.LongValue(); + const auto* a_nullable_int_arg = + encodable_a_nullable_int_arg.IsNull() + ? nullptr + : &a_nullable_int_arg_value; + const auto& encodable_a_nullable_string_arg = args.at(2); + const auto* a_nullable_string_arg = + std::get_if(&encodable_a_nullable_string_arg); + api->CallFlutterSendMultipleNullableTypesWithoutRecursion( + a_nullable_bool_arg, a_nullable_int_arg, + a_nullable_string_arg, + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterEchoBool", + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3124,17 +3940,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterEchoInt", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterEchoInt", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3161,17 +3976,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoDouble", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3199,17 +4014,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3237,17 +4052,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoUint8List", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3275,17 +4090,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterEchoList", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterEchoList", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3313,17 +4127,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterEchoMap", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterEchoMap", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3351,17 +4164,16 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( - binary_messenger, - "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." - "callFlutterEchoEnum", - &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_integration_tests." + "HostIntegrationCoreApi.callFlutterEchoEnum", + &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3389,17 +4201,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableBool", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3427,17 +4239,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableInt", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3471,17 +4283,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableDouble", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3511,17 +4323,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableString", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3551,17 +4363,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableUint8List", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3592,17 +4404,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableList", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3632,17 +4444,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableMap", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3672,17 +4484,17 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi." "callFlutterEchoNullableEnum", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -3719,7 +4531,7 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } } @@ -3750,9 +4562,13 @@ EncodableValue FlutterIntegrationCoreApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(AllNullableTypes::FromEncodableList( std::get(ReadValue(stream)))); case 130: + return CustomEncodableValue( + AllNullableTypesWithoutRecursion::FromEncodableList( + std::get(ReadValue(stream)))); + case 131: return CustomEncodableValue(AllTypes::FromEncodableList( std::get(ReadValue(stream)))); - case 131: + case 132: return CustomEncodableValue(TestMessage::FromEncodableList( std::get(ReadValue(stream)))); default: @@ -3779,15 +4595,23 @@ void FlutterIntegrationCoreApiCodecSerializer::WriteValue( stream); return; } - if (custom_value->type() == typeid(AllTypes)) { + if (custom_value->type() == typeid(AllNullableTypesWithoutRecursion)) { stream->WriteByte(130); + WriteValue(EncodableValue(std::any_cast( + *custom_value) + .ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(AllTypes)) { + stream->WriteByte(131); WriteValue(EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(TestMessage)) { - stream->WriteByte(131); + stream->WriteByte(132); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -3815,10 +4639,9 @@ void FlutterIntegrationCoreApi::Noop( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "noop"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -3848,10 +4671,9 @@ void FlutterIntegrationCoreApi::ThrowError( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "throwError"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -3882,10 +4704,9 @@ void FlutterIntegrationCoreApi::ThrowErrorFromVoid( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "throwErrorFromVoid"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -3916,12 +4737,11 @@ void FlutterIntegrationCoreApi::EchoAllTypes( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoAllTypes"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ CustomEncodableValue(everything_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -3954,12 +4774,11 @@ void FlutterIntegrationCoreApi::EchoAllNullableTypes( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoAllNullableTypes"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ everything_arg ? CustomEncodableValue(*everything_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -3993,8 +4812,7 @@ void FlutterIntegrationCoreApi::SendMultipleNullableTypes( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "sendMultipleNullableTypes"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_nullable_bool_arg ? EncodableValue(*a_nullable_bool_arg) : EncodableValue(), @@ -4003,7 +4821,7 @@ void FlutterIntegrationCoreApi::SendMultipleNullableTypes( a_nullable_string_arg ? EncodableValue(*a_nullable_string_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4029,18 +4847,99 @@ void FlutterIntegrationCoreApi::SendMultipleNullableTypes( }); } +void FlutterIntegrationCoreApi::EchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + "echoAllNullableTypesWithoutRecursion"; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + everything_arg ? CustomEncodableValue(*everything_arg) : EncodableValue(), + }); + channel.Send( + encoded_api_arguments, [channel_name, on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = + GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = + std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error( + FlutterError(std::get(list_return_value->at(0)), + std::get(list_return_value->at(1)), + list_return_value->at(2))); + } else { + const auto* return_value = + &(std::any_cast( + std::get(list_return_value->at(0)))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void FlutterIntegrationCoreApi::SendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool_arg, const int64_t* a_nullable_int_arg, + const std::string* a_nullable_string_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = + "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." + "sendMultipleNullableTypesWithoutRecursion"; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + a_nullable_bool_arg ? EncodableValue(*a_nullable_bool_arg) + : EncodableValue(), + a_nullable_int_arg ? EncodableValue(*a_nullable_int_arg) + : EncodableValue(), + a_nullable_string_arg ? EncodableValue(*a_nullable_string_arg) + : EncodableValue(), + }); + channel.Send( + encoded_api_arguments, [channel_name, on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = + GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = + std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error( + FlutterError(std::get(list_return_value->at(0)), + std::get(list_return_value->at(1)), + list_return_value->at(2))); + } else { + const auto& return_value = + std::any_cast( + std::get(list_return_value->at(0))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + void FlutterIntegrationCoreApi::EchoBool( bool a_bool_arg, std::function&& on_success, std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoBool"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_bool_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4071,12 +4970,11 @@ void FlutterIntegrationCoreApi::EchoInt( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoInt"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(an_int_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4107,16 +5005,15 @@ void FlutterIntegrationCoreApi::EchoDouble( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoDouble"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_double_arg), }); - channel->Send(encoded_api_arguments, [channel_name, - on_success = std::move(on_success), - on_error = std::move(on_error)]( - const uint8_t* reply, - size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, + on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, + size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; @@ -4144,12 +5041,11 @@ void FlutterIntegrationCoreApi::EchoString( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoString"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_string_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4182,12 +5078,11 @@ void FlutterIntegrationCoreApi::EchoUint8List( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoUint8List"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_list_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4220,12 +5115,11 @@ void FlutterIntegrationCoreApi::EchoList( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoList"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_list_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4258,12 +5152,11 @@ void FlutterIntegrationCoreApi::EchoMap( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoMap"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_map_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4295,12 +5188,11 @@ void FlutterIntegrationCoreApi::EchoEnum( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoEnum"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue((int)an_enum_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4332,16 +5224,15 @@ void FlutterIntegrationCoreApi::EchoNullableBool( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableBool"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_bool_arg ? EncodableValue(*a_bool_arg) : EncodableValue(), }); - channel->Send(encoded_api_arguments, [channel_name, - on_success = std::move(on_success), - on_error = std::move(on_error)]( - const uint8_t* reply, - size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, + on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, + size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; @@ -4368,16 +5259,15 @@ void FlutterIntegrationCoreApi::EchoNullableInt( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableInt"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ an_int_arg ? EncodableValue(*an_int_arg) : EncodableValue(), }); - channel->Send(encoded_api_arguments, [channel_name, - on_success = std::move(on_success), - on_error = std::move(on_error)]( - const uint8_t* reply, - size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, + on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, + size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; @@ -4409,12 +5299,11 @@ void FlutterIntegrationCoreApi::EchoNullableDouble( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableDouble"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_double_arg ? EncodableValue(*a_double_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4447,12 +5336,11 @@ void FlutterIntegrationCoreApi::EchoNullableString( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableString"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_string_arg ? EncodableValue(*a_string_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4485,12 +5373,11 @@ void FlutterIntegrationCoreApi::EchoNullableUint8List( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableUint8List"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_list_arg ? EncodableValue(*a_list_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4523,12 +5410,11 @@ void FlutterIntegrationCoreApi::EchoNullableList( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableList"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_list_arg ? EncodableValue(*a_list_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4561,12 +5447,11 @@ void FlutterIntegrationCoreApi::EchoNullableMap( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableMap"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ a_map_arg ? EncodableValue(*a_map_arg) : EncodableValue(), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4598,16 +5483,15 @@ void FlutterIntegrationCoreApi::EchoNullableEnum( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoNullableEnum"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ an_enum_arg ? EncodableValue((int)(*an_enum_arg)) : EncodableValue(), }); - channel->Send(encoded_api_arguments, [channel_name, - on_success = std::move(on_success), - on_error = std::move(on_error)]( - const uint8_t* reply, - size_t reply_size) { + channel.Send(encoded_api_arguments, [channel_name, + on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, + size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); const auto& encodable_return_value = *response; @@ -4640,10 +5524,9 @@ void FlutterIntegrationCoreApi::NoopAsync( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "noopAsync"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4674,12 +5557,11 @@ void FlutterIntegrationCoreApi::EchoAsyncString( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi." "echoAsyncString"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_string_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4716,12 +5598,12 @@ const flutter::StandardMessageCodec& HostTrivialApi::GetCodec() { void HostTrivialApi::SetUp(flutter::BinaryMessenger* binary_messenger, HostTrivialApi* api) { { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -4738,7 +5620,7 @@ void HostTrivialApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } } @@ -4766,12 +5648,12 @@ const flutter::StandardMessageCodec& HostSmallApi::GetCodec() { void HostSmallApi::SetUp(flutter::BinaryMessenger* binary_messenger, HostSmallApi* api) { { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -4798,16 +5680,16 @@ void HostSmallApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } { - auto channel = std::make_unique>( + BasicMessageChannel<> channel( binary_messenger, "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.voidVoid", &GetCodec()); if (api != nullptr) { - channel->SetMessageHandler( + channel.SetMessageHandler( [api](const EncodableValue& message, const flutter::MessageReply& reply) { try { @@ -4825,7 +5707,7 @@ void HostSmallApi::SetUp(flutter::BinaryMessenger* binary_messenger, } }); } else { - channel->SetMessageHandler(nullptr); + channel.SetMessageHandler(nullptr); } } } @@ -4888,12 +5770,11 @@ void FlutterSmallApi::EchoWrappedList( const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi." "echoWrappedList"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ CustomEncodableValue(msg_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { @@ -4925,12 +5806,11 @@ void FlutterSmallApi::EchoString( std::function&& on_error) { const std::string channel_name = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoString"; - auto channel = std::make_unique>( - binary_messenger_, channel_name, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(a_string_arg), }); - channel->Send( + channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]( const uint8_t* reply, size_t reply_size) { diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h index 484d5e349bcd..4b15d9dbbe70 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h @@ -168,6 +168,142 @@ class AllNullableTypes { // Constructs an object setting all fields. explicit AllNullableTypes( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const int64_t* a_nullable_int64, const double* a_nullable_double, + const std::vector* a_nullable_byte_array, + const std::vector* a_nullable4_byte_array, + const std::vector* a_nullable8_byte_array, + const std::vector* a_nullable_float_array, + const flutter::EncodableList* a_nullable_list, + const flutter::EncodableMap* a_nullable_map, + const flutter::EncodableList* nullable_nested_list, + const flutter::EncodableMap* nullable_map_with_annotations, + const flutter::EncodableMap* nullable_map_with_object, + const AnEnum* a_nullable_enum, const std::string* a_nullable_string, + const flutter::EncodableValue* a_nullable_object, + const AllNullableTypes* all_nullable_types); + + ~AllNullableTypes() = default; + AllNullableTypes(const AllNullableTypes& other); + AllNullableTypes& operator=(const AllNullableTypes& other); + AllNullableTypes(AllNullableTypes&& other) = default; + AllNullableTypes& operator=(AllNullableTypes&& other) noexcept = default; + const bool* a_nullable_bool() const; + void set_a_nullable_bool(const bool* value_arg); + void set_a_nullable_bool(bool value_arg); + + const int64_t* a_nullable_int() const; + void set_a_nullable_int(const int64_t* value_arg); + void set_a_nullable_int(int64_t value_arg); + + const int64_t* a_nullable_int64() const; + void set_a_nullable_int64(const int64_t* value_arg); + void set_a_nullable_int64(int64_t value_arg); + + const double* a_nullable_double() const; + void set_a_nullable_double(const double* value_arg); + void set_a_nullable_double(double value_arg); + + const std::vector* a_nullable_byte_array() const; + void set_a_nullable_byte_array(const std::vector* value_arg); + void set_a_nullable_byte_array(const std::vector& value_arg); + + const std::vector* a_nullable4_byte_array() const; + void set_a_nullable4_byte_array(const std::vector* value_arg); + void set_a_nullable4_byte_array(const std::vector& value_arg); + + const std::vector* a_nullable8_byte_array() const; + void set_a_nullable8_byte_array(const std::vector* value_arg); + void set_a_nullable8_byte_array(const std::vector& value_arg); + + const std::vector* a_nullable_float_array() const; + void set_a_nullable_float_array(const std::vector* value_arg); + void set_a_nullable_float_array(const std::vector& value_arg); + + const flutter::EncodableList* a_nullable_list() const; + void set_a_nullable_list(const flutter::EncodableList* value_arg); + void set_a_nullable_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableMap* a_nullable_map() const; + void set_a_nullable_map(const flutter::EncodableMap* value_arg); + void set_a_nullable_map(const flutter::EncodableMap& value_arg); + + const flutter::EncodableList* nullable_nested_list() const; + void set_nullable_nested_list(const flutter::EncodableList* value_arg); + void set_nullable_nested_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableMap* nullable_map_with_annotations() const; + void set_nullable_map_with_annotations( + const flutter::EncodableMap* value_arg); + void set_nullable_map_with_annotations( + const flutter::EncodableMap& value_arg); + + const flutter::EncodableMap* nullable_map_with_object() const; + void set_nullable_map_with_object(const flutter::EncodableMap* value_arg); + void set_nullable_map_with_object(const flutter::EncodableMap& value_arg); + + const AnEnum* a_nullable_enum() const; + void set_a_nullable_enum(const AnEnum* value_arg); + void set_a_nullable_enum(const AnEnum& value_arg); + + const std::string* a_nullable_string() const; + void set_a_nullable_string(const std::string_view* value_arg); + void set_a_nullable_string(std::string_view value_arg); + + const flutter::EncodableValue* a_nullable_object() const; + void set_a_nullable_object(const flutter::EncodableValue* value_arg); + void set_a_nullable_object(const flutter::EncodableValue& value_arg); + + const AllNullableTypes* all_nullable_types() const; + void set_all_nullable_types(const AllNullableTypes* value_arg); + void set_all_nullable_types(const AllNullableTypes& value_arg); + + private: + static AllNullableTypes FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class AllClassesWrapper; + friend class HostIntegrationCoreApi; + friend class HostIntegrationCoreApiCodecSerializer; + friend class FlutterIntegrationCoreApi; + friend class FlutterIntegrationCoreApiCodecSerializer; + friend class HostTrivialApi; + friend class HostTrivialApiCodecSerializer; + friend class HostSmallApi; + friend class HostSmallApiCodecSerializer; + friend class FlutterSmallApi; + friend class FlutterSmallApiCodecSerializer; + friend class CoreTestsTest; + std::optional a_nullable_bool_; + std::optional a_nullable_int_; + std::optional a_nullable_int64_; + std::optional a_nullable_double_; + std::optional> a_nullable_byte_array_; + std::optional> a_nullable4_byte_array_; + std::optional> a_nullable8_byte_array_; + std::optional> a_nullable_float_array_; + std::optional a_nullable_list_; + std::optional a_nullable_map_; + std::optional nullable_nested_list_; + std::optional nullable_map_with_annotations_; + std::optional nullable_map_with_object_; + std::optional a_nullable_enum_; + std::optional a_nullable_string_; + std::optional a_nullable_object_; + std::unique_ptr all_nullable_types_; +}; + +// The primary purpose for this class is to ensure coverage of Swift structs +// with nullable items, as the primary [AllNullableTypes] class is being used to +// test Swift classes. +// +// Generated class from Pigeon that represents data sent in messages. +class AllNullableTypesWithoutRecursion { + public: + // Constructs an object setting all non-nullable fields. + AllNullableTypesWithoutRecursion(); + + // Constructs an object setting all fields. + explicit AllNullableTypesWithoutRecursion( const bool* a_nullable_bool, const int64_t* a_nullable_int, const int64_t* a_nullable_int64, const double* a_nullable_double, const std::vector* a_nullable_byte_array, @@ -249,7 +385,8 @@ class AllNullableTypes { void set_a_nullable_object(const flutter::EncodableValue& value_arg); private: - static AllNullableTypes FromEncodableList(const flutter::EncodableList& list); + static AllNullableTypesWithoutRecursion FromEncodableList( + const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class AllClassesWrapper; friend class HostIntegrationCoreApi; @@ -295,11 +432,25 @@ class AllClassesWrapper { // Constructs an object setting all fields. explicit AllClassesWrapper(const AllNullableTypes& all_nullable_types, + const AllNullableTypesWithoutRecursion* + all_nullable_types_without_recursion, const AllTypes* all_types); + ~AllClassesWrapper() = default; + AllClassesWrapper(const AllClassesWrapper& other); + AllClassesWrapper& operator=(const AllClassesWrapper& other); + AllClassesWrapper(AllClassesWrapper&& other) = default; + AllClassesWrapper& operator=(AllClassesWrapper&& other) noexcept = default; const AllNullableTypes& all_nullable_types() const; void set_all_nullable_types(const AllNullableTypes& value_arg); + const AllNullableTypesWithoutRecursion* all_nullable_types_without_recursion() + const; + void set_all_nullable_types_without_recursion( + const AllNullableTypesWithoutRecursion* value_arg); + void set_all_nullable_types_without_recursion( + const AllNullableTypesWithoutRecursion& value_arg); + const AllTypes* all_types() const; void set_all_types(const AllTypes* value_arg); void set_all_types(const AllTypes& value_arg); @@ -319,8 +470,10 @@ class AllClassesWrapper { friend class FlutterSmallApi; friend class FlutterSmallApiCodecSerializer; friend class CoreTestsTest; - AllNullableTypes all_nullable_types_; - std::optional all_types_; + std::unique_ptr all_nullable_types_; + std::unique_ptr + all_nullable_types_without_recursion_; + std::unique_ptr all_types_; }; // A data class containing a List, used in unit tests. @@ -430,6 +583,10 @@ class HostIntegrationCoreApi { // Returns the passed object, to test serialization and deserialization. virtual ErrorOr> EchoAllNullableTypes( const AllNullableTypes* everything) = 0; + // Returns the passed object, to test serialization and deserialization. + virtual ErrorOr> + EchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything) = 0; // Returns the inner `aString` value from the wrapped object, to test // sending of nested objects. virtual ErrorOr> ExtractNestedNullableString( @@ -442,6 +599,11 @@ class HostIntegrationCoreApi { virtual ErrorOr SendMultipleNullableTypes( const bool* a_nullable_bool, const int64_t* a_nullable_int, const std::string* a_nullable_string) = 0; + // Returns passed in arguments of multiple types. + virtual ErrorOr + SendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string) = 0; // Returns passed in int. virtual ErrorOr> EchoNullableInt( const int64_t* a_nullable_int) = 0; @@ -534,6 +696,12 @@ class HostIntegrationCoreApi { const AllNullableTypes* everything, std::function> reply)> result) = 0; + // Returns the passed object, to test serialization and deserialization. + virtual void EchoAsyncNullableAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything, + std::function< + void(ErrorOr> reply)> + result) = 0; // Returns passed in int asynchronously. virtual void EchoAsyncNullableInt( const int64_t* an_int, @@ -596,6 +764,16 @@ class HostIntegrationCoreApi { const bool* a_nullable_bool, const int64_t* a_nullable_int, const std::string* a_nullable_string, std::function reply)> result) = 0; + virtual void CallFlutterEchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything, + std::function< + void(ErrorOr> reply)> + result) = 0; + virtual void CallFlutterSendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string, + std::function reply)> + result) = 0; virtual void CallFlutterEchoBool( bool a_bool, std::function reply)> result) = 0; virtual void CallFlutterEchoInt( @@ -712,6 +890,19 @@ class FlutterIntegrationCoreApi { const std::string* a_nullable_string, std::function&& on_success, std::function&& on_error); + // Returns the passed object, to test serialization and deserialization. + void EchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything, + std::function&& on_success, + std::function&& on_error); + // Returns passed in arguments of multiple types. + // + // Tests multiple-arity FlutterApi handling. + void SendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string, + std::function&& on_success, + std::function&& on_error); // Returns the passed boolean, to test serialization and deserialization. void EchoBool(bool a_bool, std::function&& on_success, std::function&& on_error); diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp index 46535b3be806..3b22d1e36202 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp @@ -18,6 +18,7 @@ namespace test_plugin { using core_tests_pigeontest::AllClassesWrapper; using core_tests_pigeontest::AllNullableTypes; +using core_tests_pigeontest::AllNullableTypesWithoutRecursion; using core_tests_pigeontest::AllTypes; using core_tests_pigeontest::AnEnum; using core_tests_pigeontest::ErrorOr; @@ -58,6 +59,15 @@ ErrorOr> TestPlugin::EchoAllNullableTypes( return *everything; } +ErrorOr> +TestPlugin::EchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything) { + if (!everything) { + return std::nullopt; + } + return *everything; +} + ErrorOr> TestPlugin::ThrowError() { return FlutterError("An error"); } @@ -157,6 +167,24 @@ ErrorOr TestPlugin::SendMultipleNullableTypes( return someTypes; }; +ErrorOr +TestPlugin::SendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string) { + AllNullableTypesWithoutRecursion someTypes; + someTypes.set_a_nullable_bool(a_nullable_bool); + someTypes.set_a_nullable_int(a_nullable_int); + // The string pointer can't be passed through directly since the setter for + // a string takes a std::string_view rather than std::string so the pointer + // types don't match. + if (a_nullable_string) { + someTypes.set_a_nullable_string(*a_nullable_string); + } else { + someTypes.set_a_nullable_string(nullptr); + } + return someTypes; +}; + ErrorOr> TestPlugin::EchoNullableInt( const int64_t* a_nullable_int) { if (!a_nullable_int) { @@ -328,6 +356,16 @@ void TestPlugin::EchoAsyncNullableAllNullableTypes( : std::nullopt); } +void TestPlugin::EchoAsyncNullableAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything, + std::function< + void(ErrorOr> reply)> + result) { + result(everything + ? std::optional(*everything) + : std::nullopt); +} + void TestPlugin::EchoAsyncNullableInt( const int64_t* an_int, std::function> reply)> result) { @@ -437,6 +475,31 @@ void TestPlugin::CallFlutterSendMultipleNullableTypes( [result](const FlutterError& error) { result(error); }); } +void TestPlugin::CallFlutterEchoAllNullableTypesWithoutRecursion( + const AllNullableTypesWithoutRecursion* everything, + std::function< + void(ErrorOr> reply)> + result) { + flutter_api_->EchoAllNullableTypesWithoutRecursion( + everything, + [result](const AllNullableTypesWithoutRecursion* echo) { + result(echo ? std::optional(*echo) + : std::nullopt); + }, + [result](const FlutterError& error) { result(error); }); +} + +void TestPlugin::CallFlutterSendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string, + std::function reply)> + result) { + flutter_api_->SendMultipleNullableTypesWithoutRecursion( + a_nullable_bool, a_nullable_int, a_nullable_string, + [result](const AllNullableTypesWithoutRecursion& echo) { result(echo); }, + [result](const FlutterError& error) { result(error); }); +} + void TestPlugin::CallFlutterEchoBool( bool a_bool, std::function reply)> result) { flutter_api_->EchoBool( diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h index e9fd9c6f2de5..372f7440d53f 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h @@ -39,6 +39,11 @@ class TestPlugin : public flutter::Plugin, std::optional> EchoAllNullableTypes( const core_tests_pigeontest::AllNullableTypes* everything) override; + core_tests_pigeontest::ErrorOr< + std::optional> + EchoAllNullableTypesWithoutRecursion( + const core_tests_pigeontest::AllNullableTypesWithoutRecursion* everything) + override; core_tests_pigeontest::ErrorOr> ThrowError() override; std::optional ThrowErrorFromVoid() @@ -78,6 +83,11 @@ class TestPlugin : public flutter::Plugin, SendMultipleNullableTypes(const bool* a_nullable_bool, const int64_t* a_nullable_int, const std::string* a_nullable_string) override; + core_tests_pigeontest::ErrorOr< + core_tests_pigeontest::AllNullableTypesWithoutRecursion> + SendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string) override; core_tests_pigeontest::ErrorOr> EchoNullableInt( const int64_t* a_nullable_int) override; core_tests_pigeontest::ErrorOr> EchoNullableDouble( @@ -130,6 +140,13 @@ class TestPlugin : public flutter::Plugin, std::optional> reply)> result) override; + void EchoAsyncNullableAllNullableTypesWithoutRecursion( + const core_tests_pigeontest::AllNullableTypesWithoutRecursion* everything, + std::function< + void(core_tests_pigeontest::ErrorOr> + reply)> + result) override; void EchoAsyncInt( int64_t an_int, std::function reply)> result) @@ -253,6 +270,21 @@ class TestPlugin : public flutter::Plugin, core_tests_pigeontest::AllNullableTypes> reply)> result) override; + void CallFlutterEchoAllNullableTypesWithoutRecursion( + const core_tests_pigeontest::AllNullableTypesWithoutRecursion* everything, + std::function< + void(core_tests_pigeontest::ErrorOr> + reply)> + result) override; + void CallFlutterSendMultipleNullableTypesWithoutRecursion( + const bool* a_nullable_bool, const int64_t* a_nullable_int, + const std::string* a_nullable_string, + std::function< + void(core_tests_pigeontest::ErrorOr< + core_tests_pigeontest::AllNullableTypesWithoutRecursion> + reply)> + result) override; void CallFlutterEchoBool( bool a_bool, std::function reply)> result) diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 9693bac54588..b4912fd32620 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22 -version: 17.0.0 # This must match the version in lib/generator_tools.dart +version: 17.3.0 # This must match the version in lib/generator_tools.dart environment: sdk: ^3.1.0 @@ -10,7 +10,9 @@ environment: dependencies: analyzer: ">=5.13.0 <7.0.0" args: ^2.1.0 + code_builder: ^4.10.0 collection: ^1.15.0 + dart_style: ^2.3.4 meta: ^1.9.0 path: ^1.8.0 yaml: ^3.1.1 diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index 42aa28c5361a..c2fdcd68f689 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -584,11 +584,12 @@ void main() { contains('void set_nullable_string(std::string_view value_arg)')); expect( code, contains('void set_nullable_nested(const Nested& value_arg)')); - // Instance variables should be std::optionals. + // Most instance variables should be std::optionals. expect(code, contains('std::optional nullable_bool_')); expect(code, contains('std::optional nullable_int_')); expect(code, contains('std::optional nullable_string_')); - expect(code, contains('std::optional nullable_nested_')); + // Custom classes are the exception, to avoid inline storage. + expect(code, contains('std::unique_ptr nullable_nested_')); } { final StringBuffer sink = StringBuffer(); @@ -624,10 +625,7 @@ void main() { code, contains( 'return nullable_string_ ? &(*nullable_string_) : nullptr;')); - expect( - code, - contains( - 'return nullable_nested_ ? &(*nullable_nested_) : nullptr;')); + expect(code, contains('return nullable_nested_.get();')); // Setters convert to optionals. expect( code, @@ -643,8 +641,8 @@ void main() { 'std::optional(*value_arg) : std::nullopt;')); expect( code, - contains('nullable_nested_ = value_arg ? ' - 'std::optional(*value_arg) : std::nullopt;')); + contains( + 'nullable_nested_ = value_arg ? std::make_unique(*value_arg) : nullptr;')); // Serialization handles optionals. expect( code, @@ -763,7 +761,8 @@ void main() { expect(code, contains('bool non_nullable_bool_;')); expect(code, contains('int64_t non_nullable_int_;')); expect(code, contains('std::string non_nullable_string_;')); - expect(code, contains('Nested non_nullable_nested_;')); + // Except for custom classes. + expect(code, contains('std::unique_ptr non_nullable_nested_;')); } { final StringBuffer sink = StringBuffer(); @@ -793,15 +792,20 @@ void main() { expect(code, contains('return non_nullable_bool_;')); expect(code, contains('return non_nullable_int_;')); expect(code, contains('return non_nullable_string_;')); - expect(code, contains('return non_nullable_nested_;')); + // Unless it's a custom class. + expect(code, contains('return *non_nullable_nested_;')); // Setters just assign the value. expect(code, contains('non_nullable_bool_ = value_arg;')); expect(code, contains('non_nullable_int_ = value_arg;')); expect(code, contains('non_nullable_string_ = value_arg;')); - expect(code, contains('non_nullable_nested_ = value_arg;')); + // Unless it's a custom class. + expect( + code, + contains( + 'non_nullable_nested_ = std::make_unique(value_arg);')); // Serialization uses the value directly. expect(code, contains('EncodableValue(non_nullable_bool_)')); - expect(code, contains('non_nullable_nested_.ToEncodableList()')); + expect(code, contains('non_nullable_nested_->ToEncodableList()')); // Serialization should use push_back, not initializer lists, to avoid // copies. @@ -2114,4 +2118,51 @@ void main() { '"Unable to establish connection on channel: \'" + channel_name + "\'."')); expect(code, contains('on_error(CreateConnectionError(channel_name));')); }); + + test('stack allocates the message channel.', () { + final Root root = Root( + apis: [ + AstFlutterApi( + name: 'Api', + methods: [ + Method( + name: 'method', + location: ApiLocation.flutter, + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [ + Parameter( + name: 'field', + type: const TypeDeclaration( + baseName: 'int', + isNullable: true, + ), + ), + ], + ) + ], + ) + ], + classes: [], + enums: [], + ); + final StringBuffer sink = StringBuffer(); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate( + generatorOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect( + code, + contains( + 'BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec());')); + expect(code, contains('channel.Send')); + }); } diff --git a/packages/pigeon/test/dart/proxy_api_test.dart b/packages/pigeon/test/dart/proxy_api_test.dart new file mode 100644 index 000000000000..fb24a2038615 --- /dev/null +++ b/packages/pigeon/test/dart/proxy_api_test.dart @@ -0,0 +1,1014 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/ast.dart'; +import 'package:pigeon/dart_generator.dart'; +import 'package:test/test.dart'; + +const String DEFAULT_PACKAGE_NAME = 'test_package'; + +void main() { + group('ProxyApi', () { + test('one api', () { + final Root root = Root(apis: [ + AstProxyApi(name: 'Api', constructors: [ + Constructor(name: 'name', parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'Input', + isNullable: false, + ), + name: 'input', + ), + ]), + ], fields: [ + ApiField( + name: 'someField', + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ) + ], methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'Input', + isNullable: false, + ), + name: 'input', + ) + ], + returnType: const TypeDeclaration( + baseName: 'String', + isNullable: false, + ), + ), + Method( + name: 'doSomethingElse', + location: ApiLocation.flutter, + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'Input', + isNullable: false, + ), + name: 'input', + ) + ], + returnType: const TypeDeclaration( + baseName: 'String', + isNullable: false, + ), + isRequired: false, + ), + ]) + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + + // Instance Manager + expect(code, contains(r'class PigeonInstanceManager')); + expect(code, contains(r'class _PigeonInstanceManagerApi')); + + // Base Api class + expect( + code, + contains(r'abstract class PigeonProxyApiBaseClass'), + ); + + // Codec and class + expect(code, contains('class _PigeonProxyApiBaseCodec')); + expect(code, contains(r'class Api extends PigeonProxyApiBaseClass')); + + // Constructors + expect( + collapsedCode, + contains( + r'Api.name({ super.pigeon_binaryMessenger, super.pigeon_instanceManager, required this.someField, this.doSomethingElse, required Input input, })', + ), + ); + expect( + code, + contains( + r'Api.pigeon_detached', + ), + ); + + // Field + expect(code, contains('final int someField;')); + + // Dart -> Host method + expect(code, contains('Future doSomething(Input input)')); + + // Host -> Dart method + expect(code, contains(r'static void pigeon_setUpMessageHandlers({')); + expect( + collapsedCode, + contains( + 'final String Function( Api pigeon_instance, Input input, )? doSomethingElse;', + ), + ); + + // Copy method + expect(code, contains(r'Api pigeon_copy(')); + }); + + group('inheritance', () { + test('extends', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ); + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [], + superClass: TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ), + ), + api2, + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains(r'class Api extends Api2')); + expect( + collapsedCode, + contains( + r'Api.pigeon_detached({ super.pigeon_binaryMessenger, super.pigeon_instanceManager, }) : super.pigeon_detached();', + ), + ); + }); + + test('implements', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ); + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [], + interfaces: { + TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ) + }, + ), + api2, + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect( + code, + contains( + r'class Api extends PigeonProxyApiBaseClass implements Api2', + ), + ); + }); + + test('implements 2 ProxyApis', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ); + final AstProxyApi api3 = AstProxyApi( + name: 'Api3', + constructors: [], + fields: [], + methods: [], + ); + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [], + interfaces: { + TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ), + TypeDeclaration( + baseName: 'Api3', + isNullable: false, + associatedProxyApi: api2, + ), + }, + ), + api2, + api3, + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect( + code, + contains( + r'class Api extends PigeonProxyApiBaseClass implements Api2, Api3', + ), + ); + }); + + test('implements inherits flutter methods', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [ + Method( + name: 'aFlutterMethod', + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [], + location: ApiLocation.flutter, + ), + Method( + name: 'aNullableFlutterMethod', + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [], + location: ApiLocation.flutter, + isRequired: false, + ), + ], + ); + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [], + interfaces: { + TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ) + }, + ), + api2, + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect( + code, + contains( + r'class Api extends PigeonProxyApiBaseClass implements Api2', + ), + ); + expect( + collapsedCode, + contains( + r'Api.pigeon_detached({ super.pigeon_binaryMessenger, ' + r'super.pigeon_instanceManager, ' + r'required this.aFlutterMethod, ' + r'this.aNullableFlutterMethod, })', + ), + ); + }); + }); + + group('Constructors', () { + test('empty name and no params constructor', () { + final Root root = Root( + apis: [ + AstProxyApi(name: 'Api', constructors: [ + Constructor( + name: '', + parameters: [], + ) + ], fields: [], methods: []), + ], + classes: [], + enums: [], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'Api({ super.pigeon_binaryMessenger, ' + r'super.pigeon_instanceManager, })', + ), + ); + expect( + collapsedCode, + contains( + r"const String __pigeon_channelName = 'dev.flutter.pigeon.test_package.Api.pigeon_defaultConstructor';", + ), + ); + expect( + collapsedCode, + contains( + r'__pigeon_channel .send([__pigeon_instanceIdentifier])', + ), + ); + }); + + test('multiple params constructor', () { + final Enum anEnum = Enum( + name: 'AnEnum', + members: [EnumMember(name: 'one')], + ); + final Root root = Root( + apis: [ + AstProxyApi(name: 'Api', constructors: [ + Constructor( + name: 'name', + parameters: [ + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'int', + ), + name: 'validType', + ), + Parameter( + type: TypeDeclaration( + isNullable: false, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'enumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'Api2', + ), + name: 'proxyApiType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'int', + ), + name: 'nullableValidType', + ), + Parameter( + type: TypeDeclaration( + isNullable: true, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'nullableEnumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'Api2', + ), + name: 'nullableProxyApiType', + ), + ], + ) + ], fields: [], methods: []), + AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ), + ], + classes: [], + enums: [anEnum], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'Api.name({ super.pigeon_binaryMessenger, ' + r'super.pigeon_instanceManager, ' + r'required int validType, ' + r'required AnEnum enumType, ' + r'required Api2 proxyApiType, ' + r'int? nullableValidType, ' + r'AnEnum? nullableEnumType, ' + r'Api2? nullableProxyApiType, })', + ), + ); + expect( + collapsedCode, + contains( + r'__pigeon_channel.send([ ' + r'__pigeon_instanceIdentifier, ' + r'validType, enumType.index, proxyApiType, ' + r'nullableValidType, nullableEnumType?.index, nullableProxyApiType ])', + ), + ); + }); + }); + + group('Fields', () { + test('constructor with fields', () { + final Enum anEnum = Enum( + name: 'AnEnum', + members: [EnumMember(name: 'one')], + ); + final Root root = Root( + apis: [ + AstProxyApi( + name: 'Api', + constructors: [ + Constructor( + name: 'name', + parameters: [], + ) + ], + fields: [ + ApiField( + type: const TypeDeclaration( + isNullable: false, + baseName: 'int', + ), + name: 'validType', + ), + ApiField( + type: TypeDeclaration( + isNullable: false, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'enumType', + ), + ApiField( + type: const TypeDeclaration( + isNullable: false, + baseName: 'Api2', + ), + name: 'proxyApiType', + ), + ApiField( + type: const TypeDeclaration( + isNullable: true, + baseName: 'int', + ), + name: 'nullableValidType', + ), + ApiField( + type: TypeDeclaration( + isNullable: true, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'nullableEnumType', + ), + ApiField( + type: const TypeDeclaration( + isNullable: true, + baseName: 'Api2', + ), + name: 'nullableProxyApiType', + ), + ], + methods: [], + ), + AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ), + ], + classes: [], + enums: [anEnum], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'Api.name({ super.pigeon_binaryMessenger, ' + r'super.pigeon_instanceManager, ' + r'required this.validType, ' + r'required this.enumType, ' + r'required this.proxyApiType, ' + r'this.nullableValidType, ' + r'this.nullableEnumType, ' + r'this.nullableProxyApiType, })', + ), + ); + expect( + collapsedCode, + contains( + r'__pigeon_channel.send([ ' + r'__pigeon_instanceIdentifier, ' + r'validType, enumType.index, proxyApiType, ' + r'nullableValidType, nullableEnumType?.index, nullableProxyApiType ])', + ), + ); + expect( + code, + contains(r'final int validType;'), + ); + expect( + code, + contains(r'final AnEnum enumType;'), + ); + expect( + code, + contains(r'final Api2 proxyApiType;'), + ); + expect( + code, + contains(r'final int? nullableValidType;'), + ); + expect( + code, + contains(r'final AnEnum? nullableEnumType;'), + ); + expect( + code, + contains(r'final Api2? nullableProxyApiType;'), + ); + }); + + test('attached field', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ); + final Root root = Root( + apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [ + ApiField( + name: 'aField', + isAttached: true, + type: TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ), + ), + ], + methods: [], + ), + api2, + ], + classes: [], + enums: [], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, contains('class Api')); + expect(code, contains(r'late final Api2 aField = __pigeon_aField();')); + expect(code, contains(r'Api2 __pigeon_aField()')); + }); + + test('static attached field', () { + final AstProxyApi api2 = AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ); + final Root root = Root( + apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [ + ApiField( + name: 'aField', + isStatic: true, + isAttached: true, + type: TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: api2, + ), + ), + ], + methods: [], + ), + api2, + ], + classes: [], + enums: [], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, contains('class Api')); + expect( + code, contains(r'static final Api2 aField = __pigeon_aField();')); + expect(code, contains(r'static Api2 __pigeon_aField()')); + }); + }); + + group('Host methods', () { + test('multiple params method', () { + final Enum anEnum = Enum( + name: 'AnEnum', + members: [EnumMember(name: 'one')], + ); + final Root root = Root( + apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'int', + ), + name: 'validType', + ), + Parameter( + type: TypeDeclaration( + isNullable: false, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'enumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'Api2', + ), + name: 'proxyApiType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'int', + ), + name: 'nullableValidType', + ), + Parameter( + type: TypeDeclaration( + isNullable: true, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'nullableEnumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'Api2', + ), + name: 'nullableProxyApiType', + ), + ], + returnType: const TypeDeclaration.voidDeclaration(), + ), + ], + ), + AstProxyApi( + name: 'Api2', + constructors: [], + fields: [], + methods: [], + ), + ], + classes: [], + enums: [anEnum], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'Future doSomething( int validType, AnEnum enumType, ' + r'Api2 proxyApiType, int? nullableValidType, ' + r'AnEnum? nullableEnumType, Api2? nullableProxyApiType, )', + ), + ); + expect( + collapsedCode, + contains( + r'await __pigeon_channel.send([ this, validType, ' + r'enumType.index, proxyApiType, nullableValidType, ' + r'nullableEnumType?.index, nullableProxyApiType ])', + ), + ); + }); + + test('static method', () { + final Root root = Root( + apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + isStatic: true, + parameters: [], + returnType: const TypeDeclaration.voidDeclaration(), + ), + ], + ), + ], + classes: [], + enums: [], + ); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'static Future doSomething({ BinaryMessenger? pigeon_binaryMessenger, ' + r'PigeonInstanceManager? pigeon_instanceManager, })', + ), + ); + expect( + collapsedCode, + contains(r'await __pigeon_channel.send(null)'), + ); + }); + }); + + group('Flutter methods', () { + test('multiple params flutter method', () { + final Enum anEnum = Enum( + name: 'AnEnum', + members: [EnumMember(name: 'one')], + ); + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [ + Method( + name: 'doSomething', + location: ApiLocation.flutter, + isRequired: false, + parameters: [ + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'int', + ), + name: 'validType', + ), + Parameter( + type: TypeDeclaration( + isNullable: false, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'enumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: false, + baseName: 'Api2', + ), + name: 'proxyApiType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'int', + ), + name: 'nullableValidType', + ), + Parameter( + type: TypeDeclaration( + isNullable: true, + baseName: 'AnEnum', + associatedEnum: anEnum, + ), + name: 'nullableEnumType', + ), + Parameter( + type: const TypeDeclaration( + isNullable: true, + baseName: 'Api2', + ), + name: 'nullableProxyApiType', + ), + ], + returnType: const TypeDeclaration.voidDeclaration(), + ), + ]) + ], classes: [], enums: [ + anEnum + ]); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + expect(code, contains('class Api')); + expect( + collapsedCode, + contains( + r'final void Function( Api pigeon_instance, int validType, ' + r'AnEnum enumType, Api2 proxyApiType, int? nullableValidType, ' + r'AnEnum? nullableEnumType, Api2? nullableProxyApiType, )? ' + r'doSomething;', + ), + ); + expect( + collapsedCode, + contains( + r'void Function( Api pigeon_instance, int validType, AnEnum enumType, ' + r'Api2 proxyApiType, int? nullableValidType, ' + r'AnEnum? nullableEnumType, Api2? nullableProxyApiType, )? ' + r'doSomething'), + ); + expect( + code, + contains(r'final Api? arg_pigeon_instance = (args[0] as Api?);'), + ); + expect( + code, + contains(r'final int? arg_validType = (args[1] as int?);'), + ); + expect( + collapsedCode, + contains( + r'final AnEnum? arg_enumType = args[2] == null ? ' + r'null : AnEnum.values[args[2]! as int];', + ), + ); + expect( + code, + contains(r'final Api2? arg_proxyApiType = (args[3] as Api2?);'), + ); + expect( + code, + contains(r'final int? arg_nullableValidType = (args[4] as int?);'), + ); + expect( + collapsedCode, + contains( + r'final AnEnum? arg_nullableEnumType = args[5] == null ? ' + r'null : AnEnum.values[args[5]! as int];', + ), + ); + expect( + collapsedCode, + contains( + r'(doSomething ?? arg_pigeon_instance!.doSomething)?.call( arg_pigeon_instance!, ' + r'arg_validType!, arg_enumType!, arg_proxyApiType!, ' + r'arg_nullableValidType, arg_nullableEnumType, ' + r'arg_nullableProxyApiType);', + ), + ); + }); + }); + }); +} + +/// Replaces a new line and the indentation with a single white space +/// +/// This +/// +/// ```dart +/// void method( +/// int param1, +/// int param2, +/// ) +/// ``` +/// +/// converts to +/// +/// ```dart +/// void method( int param1, int param2, ) +/// ``` +String _collapseNewlineAndIndentation(String string) { + final StringBuffer result = StringBuffer(); + for (final String line in string.split('\n')) { + result.write('${line.trimLeft()} '); + } + return result.toString().trim(); +} diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index 6b43b93eecc5..e68bb96239c1 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -404,10 +404,8 @@ void main() { ), ); - final List apiChain = recursiveGetSuperClassApisChain(api); - expect( - apiChain, + api.allSuperClasses().toList(), containsAllInOrder([ superClassApi, superClassOfSuperClassApi, @@ -478,10 +476,8 @@ void main() { }, ); - final Set allInterfaces = recursiveFindAllInterfaceApis(api); - expect( - allInterfaces, + api.apisOfInterfaces(), containsAll([ interfaceApi, interfaceApi2, @@ -523,9 +519,6 @@ void main() { TypeDeclaration(baseName: 'A', isNullable: false, associatedProxyApi: a), }; - expect( - () => recursiveFindAllInterfaceApis(a), - throwsArgumentError, - ); + expect(() => a.apisOfInterfaces(), throwsArgumentError); }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index 267747e1e913..7717f0ca8779 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -2931,4 +2931,178 @@ void main() { 'return [FlutterError errorWithCode:@"channel-error" message:[NSString stringWithFormat:@"%@/%@/%@", @"Unable to establish connection on channel: \'", channelName, @"\'."] details:@""]')); expect(code, contains('completion(createConnectionError(channelName))')); }); + + test('header of FlutterApi uses correct enum name with prefix', () { + final Enum enum1 = Enum( + name: 'Enum1', + members: [ + EnumMember(name: 'one'), + EnumMember(name: 'two'), + ], + ); + final Root root = Root(apis: [ + AstFlutterApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.flutter, + isAsynchronous: true, + parameters: [], + returnType: TypeDeclaration( + baseName: 'Enum1', + isNullable: false, + associatedEnum: enum1, + ), + ) + ]), + ], classes: [], enums: [ + enum1, + ]); + final StringBuffer sink = StringBuffer(); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(prefix: 'FLT'), + ); + generator.generate( + generatorOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, isNot(contains('FLTFLT'))); + expect(code, contains('FLTEnum1Box')); + }); + + test('source of FlutterApi uses correct enum name with prefix', () { + final Enum enum1 = Enum( + name: 'Enum1', + members: [ + EnumMember(name: 'one'), + EnumMember(name: 'two'), + ], + ); + final Root root = Root(apis: [ + AstFlutterApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.flutter, + isAsynchronous: true, + parameters: [], + returnType: TypeDeclaration( + baseName: 'Enum1', + isNullable: false, + associatedEnum: enum1, + ), + ) + ]), + ], classes: [], enums: [ + enum1, + ]); + final StringBuffer sink = StringBuffer(); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(prefix: 'FLT'), + ); + generator.generate( + generatorOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, isNot(contains('FLTFLT'))); + expect(code, contains('FLTEnum1Box')); + }); + + test('header of HostApi uses correct enum name with prefix', () { + final Enum enum1 = Enum( + name: 'Enum1', + members: [ + EnumMember(name: 'one'), + EnumMember(name: 'two'), + ], + ); + final TypeDeclaration enumType = TypeDeclaration( + baseName: 'Enum1', + isNullable: false, + associatedEnum: enum1, + ); + final Root root = Root(apis: [ + AstHostApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + isAsynchronous: true, + parameters: [Parameter(name: 'value', type: enumType)], + returnType: enumType, + ) + ]), + ], classes: [], enums: [ + enum1, + ]); + final StringBuffer sink = StringBuffer(); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(prefix: 'FLT'), + ); + generator.generate( + generatorOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, isNot(contains('FLTFLT'))); + expect(code, contains('FLTEnum1Box')); + }); + + test('source of HostApi uses correct enum name with prefix', () { + final Enum enum1 = Enum( + name: 'Enum1', + members: [ + EnumMember(name: 'one'), + EnumMember(name: 'two'), + ], + ); + final TypeDeclaration enumType = TypeDeclaration( + baseName: 'Enum1', + isNullable: false, + associatedEnum: enum1, + ); + final Root root = Root(apis: [ + AstHostApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + isAsynchronous: true, + parameters: [Parameter(name: 'value', type: enumType)], + returnType: enumType, + ) + ]), + ], classes: [], enums: [ + enum1, + ]); + final StringBuffer sink = StringBuffer(); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(prefix: 'FLT'), + ); + generator.generate( + generatorOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, isNot(contains('FLTFLT'))); + expect(code, contains('FLTEnum1Box')); + }); } diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 9915e26aa57d..1cd3b6e5ea9b 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -68,6 +68,7 @@ Future generateTestPigeons({required String baseDir}) async { 'null_fields', 'nullable_returns', 'primitive', + 'proxy_api_tests', ]; final String outputBase = p.join(baseDir, 'platform_tests', 'test_plugin'); @@ -81,6 +82,13 @@ Future generateTestPigeons({required String baseDir}) async { final Set skipLanguages = _unsupportedFiles[input] ?? {}; + final bool kotlinErrorClassGenerationTestFiles = + input == 'core_tests' || input == 'background_platform_channels'; + + final String kotlinErrorName = kotlinErrorClassGenerationTestFiles + ? 'FlutterError' + : '${pascalCaseName}Error'; + // Generate the default language test plugin output. int generateCode = await runPigeon( input: './pigeons/$input.dart', @@ -90,7 +98,8 @@ Future generateTestPigeons({required String baseDir}) async { ? null : '$outputBase/android/src/main/kotlin/com/example/test_plugin/$pascalCaseName.gen.kt', kotlinPackage: 'com.example.test_plugin', - kotlinErrorClassName: '${pascalCaseName}Error', + kotlinErrorClassName: kotlinErrorName, + kotlinIncludeErrorClass: input != 'core_tests', // iOS swiftOut: skipLanguages.contains(GeneratorLanguage.swift) ? null @@ -143,6 +152,7 @@ Future generateTestPigeons({required String baseDir}) async { objcSourceOut: skipLanguages.contains(GeneratorLanguage.objc) ? null : '$alternateOutputBase/ios/Classes/$pascalCaseName.gen.m', + objcPrefix: input == 'core_tests' ? 'FLT' : '', suppressVersion: true, dartPackageName: 'pigeon_integration_tests', ); @@ -176,6 +186,7 @@ Future runPigeon({ String? kotlinOut, String? kotlinPackage, String? kotlinErrorClassName, + bool kotlinIncludeErrorClass = true, String? swiftOut, String? cppHeaderOut, String? cppSourceOut, @@ -217,7 +228,10 @@ Future runPigeon({ javaOptions: JavaOptions(package: javaPackage), kotlinOut: kotlinOut, kotlinOptions: KotlinOptions( - package: kotlinPackage, errorClassName: kotlinErrorClassName), + package: kotlinPackage, + errorClassName: kotlinErrorClassName, + includeErrorClass: kotlinIncludeErrorClass, + ), objcHeaderOut: objcHeaderOut, objcSourceOut: objcSourceOut, objcOptions: ObjcOptions(prefix: objcPrefix), diff --git a/packages/pigeon/tool/shared/process_utils.dart b/packages/pigeon/tool/shared/process_utils.dart index 28c816f18b1b..e38a4af777f9 100644 --- a/packages/pigeon/tool/shared/process_utils.dart +++ b/packages/pigeon/tool/shared/process_utils.dart @@ -16,13 +16,29 @@ Future runProcess(String command, List arguments, mode: streamOutput ? ProcessStartMode.inheritStdio : ProcessStartMode.normal, ); + + if (streamOutput) { + return process.exitCode; + } + + final List stdoutBuffer = []; + final List stderrBuffer = []; + final Future stdoutFuture = process.stdout.forEach(stdoutBuffer.addAll); + final Future stderrFuture = process.stderr.forEach(stderrBuffer.addAll); final int exitCode = await process.exitCode; + await Future.wait(>[ + stdoutFuture, + stderrFuture, + ]); + if (exitCode != 0 && logFailure) { // ignore: avoid_print print('$command $arguments failed:'); + stdout.add(stdoutBuffer); + stderr.add(stderrBuffer); await Future.wait(>[ - process.stdout.pipe(stdout), - process.stderr.pipe(stderr), + stdout.flush(), + stderr.flush(), ]); } return exitCode; diff --git a/packages/platform/example/windows/flutter/CMakeLists.txt b/packages/platform/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/platform/example/windows/flutter/CMakeLists.txt +++ b/packages/platform/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md index 28955127a78e..58a970e78f7d 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.10.2 + +* Updates web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. + ## 0.10.1+2 * Fixes "width and height missing" warning on web. diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart index d712f6fbfa00..35fe738ecfed 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart @@ -75,7 +75,10 @@ void main() { expect(element.tagName.toLowerCase(), 'flt-semantics'); expect(element.getAttribute('aria-label'), 'Works As Expected'); - }); + }, + // TODO(bparrishMines): The semantics label is returning null. + // See https://github.com/flutter/flutter/issues/145238 + skip: true); testWidgets( 'finds semantics of wrapped widgets with intercepting set to false', @@ -88,7 +91,10 @@ void main() { expect(element.tagName.toLowerCase(), 'flt-semantics'); expect(element.getAttribute('aria-label'), 'Never calls onPressed transparent'); - }); + }, + // TODO(bparrishMines): The semantics label is returning null. + // See https://github.com/flutter/flutter/issues/145238 + skip: true); testWidgets('finds semantics of unwrapped elements', (WidgetTester tester) async { @@ -99,7 +105,10 @@ void main() { expect(element.tagName.toLowerCase(), 'flt-semantics'); expect(element.getAttribute('aria-label'), 'Never calls onPressed'); - }); + }, + // TODO(bparrishMines): The semantics label is returning null. + // See https://github.com/flutter/flutter/issues/145238 + skip: true); // Notice that, when hit-testing the background platform view, instead of // finding a semantics node, the platform view itself is found. This is diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml index 2132641ca791..06625c07fbb7 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml @@ -3,8 +3,8 @@ description: "Demonstrates how to use the pointer_interceptor_web plugin." publish_to: 'none' environment: - sdk: ^3.2.0 - flutter: '>=3.16.0' + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: cupertino_icons: ^1.0.2 @@ -13,7 +13,7 @@ dependencies: pointer_interceptor_platform_interface: ^0.10.0 pointer_interceptor_web: path: ../../pointer_interceptor_web - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml index ff1f0747d9d5..83e41de9e23e 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml @@ -2,11 +2,11 @@ name: pointer_interceptor_web description: Web implementation of the pointer_interceptor plugin. repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor/pointer_interceptor_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apointer_interceptor -version: 0.10.1+2 +version: 0.10.2 environment: - sdk: ^3.2.0 - flutter: '>=3.16.0' + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -23,7 +23,7 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.7 pointer_interceptor_platform_interface: ^0.10.0 - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/rfw/.gitignore b/packages/rfw/.gitignore index 01f61108a475..0b90ed48e5ab 100644 --- a/packages/rfw/.gitignore +++ b/packages/rfw/.gitignore @@ -76,3 +76,6 @@ build/ !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 + +# Coverage +coverage/ diff --git a/packages/rfw/CHANGELOG.md b/packages/rfw/CHANGELOG.md index 8db008e12bee..7f1da5e2f35b 100644 --- a/packages/rfw/CHANGELOG.md +++ b/packages/rfw/CHANGELOG.md @@ -1,3 +1,16 @@ +## 1.0.26 +* Supports overriding the error widget builder. + +## 1.0.25 +* Adds support for wildget builders. + +## 1.0.24 + +* Adds `InkResponse` material widget. +* Adds `Material` material widget. +* Adds the `child` to `Opacity` core widget. +* Implements more `InkWell` parameters. + ## 1.0.23 * Replaces usage of deprecated Flutter APIs. diff --git a/packages/rfw/example/hello/windows/flutter/CMakeLists.txt b/packages/rfw/example/hello/windows/flutter/CMakeLists.txt index b02c5485c957..86edc67b995f 100644 --- a/packages/rfw/example/hello/windows/flutter/CMakeLists.txt +++ b/packages/rfw/example/hello/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/rfw/example/hello/windows/runner/Runner.rc b/packages/rfw/example/hello/windows/runner/Runner.rc index c9091ea41b5d..a56c2a14c150 100644 --- a/packages/rfw/example/hello/windows/runner/Runner.rc +++ b/packages/rfw/example/hello/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/rfw/example/local/windows/flutter/CMakeLists.txt b/packages/rfw/example/local/windows/flutter/CMakeLists.txt index b02c5485c957..86edc67b995f 100644 --- a/packages/rfw/example/local/windows/flutter/CMakeLists.txt +++ b/packages/rfw/example/local/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/rfw/example/local/windows/runner/Runner.rc b/packages/rfw/example/local/windows/runner/Runner.rc index 0ca2e22abbca..dfe34f81aae7 100644 --- a/packages/rfw/example/local/windows/runner/Runner.rc +++ b/packages/rfw/example/local/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/rfw/example/remote/windows/flutter/CMakeLists.txt b/packages/rfw/example/remote/windows/flutter/CMakeLists.txt index b02c5485c957..86edc67b995f 100644 --- a/packages/rfw/example/remote/windows/flutter/CMakeLists.txt +++ b/packages/rfw/example/remote/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/rfw/example/remote/windows/runner/Runner.rc b/packages/rfw/example/remote/windows/runner/Runner.rc index 991f8f5ab578..5f0cfb204f38 100644 --- a/packages/rfw/example/remote/windows/runner/Runner.rc +++ b/packages/rfw/example/remote/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt b/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt index b02c5485c957..86edc67b995f 100644 --- a/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt +++ b/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/rfw/example/wasm/windows/runner/Runner.rc b/packages/rfw/example/wasm/windows/runner/Runner.rc index 7535ea4587ed..c2d9cad1cb97 100644 --- a/packages/rfw/example/wasm/windows/runner/Runner.rc +++ b/packages/rfw/example/wasm/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/rfw/lib/src/dart/binary.dart b/packages/rfw/lib/src/dart/binary.dart index f36690f2c7d0..cd999377f207 100644 --- a/packages/rfw/lib/src/dart/binary.dart +++ b/packages/rfw/lib/src/dart/binary.dart @@ -298,6 +298,8 @@ const int _msEvent = 0x0E; const int _msSwitch = 0x0F; const int _msDefault = 0x10; const int _msSetState = 0x11; +const int _msWidgetBuilder = 0x12; +const int _msWidgetBuilderArgReference = 0x13; /// API for decoding Remote Flutter Widgets binary blobs. /// @@ -453,6 +455,10 @@ class _BlobDecoder { return _readSwitch(); case _msSetState: return SetStateHandler(StateReference(_readPartList()), _readArgument()); + case _msWidgetBuilder: + return _readWidgetBuilder(); + case _msWidgetBuilderArgReference: + return WidgetBuilderArgReference(_readString(), _readPartList()); default: return _parseValue(type, _readArgument); } @@ -468,6 +474,16 @@ class _BlobDecoder { return ConstructorCall(name, _readMap(_readArgument)!); } + WidgetBuilderDeclaration _readWidgetBuilder() { + final String argumentName = _readString(); + final int type = _readByte(); + if (type != _msWidget && type != _msSwitch) { + throw FormatException('Unrecognized data type 0x${type.toRadixString(16).toUpperCase().padLeft(2, "0")} while decoding widget builder blob.'); + } + final BlobNode widget = type == _msWidget ? _readWidget() : _readSwitch(); + return WidgetBuilderDeclaration(argumentName, widget); + } + WidgetDeclaration _readDeclaration() { final String name = _readString(); final DynamicMap? initialState = _readMap(readValue, nullIfEmpty: true); @@ -613,6 +629,10 @@ class _BlobEncoder { bytes.addByte(_msWidget); _writeString(value.name); _writeMap(value.arguments, _writeArgument); + } else if (value is WidgetBuilderDeclaration) { + bytes.addByte(_msWidgetBuilder); + _writeString(value.argumentName); + _writeArgument(value.widget); } else if (value is ArgsReference) { bytes.addByte(_msArgsReference); _writeInt64(value.parts.length); @@ -621,6 +641,11 @@ class _BlobEncoder { bytes.addByte(_msDataReference); _writeInt64(value.parts.length); value.parts.forEach(_writePart); + } else if (value is WidgetBuilderArgReference) { + bytes.addByte(_msWidgetBuilderArgReference); + _writeString(value.argumentName); + _writeInt64(value.parts.length); + value.parts.forEach(_writePart); } else if (value is LoopReference) { bytes.addByte(_msLoopReference); _writeInt64(value.loop); diff --git a/packages/rfw/lib/src/dart/model.dart b/packages/rfw/lib/src/dart/model.dart index b6a6b9921263..22be39c3acb7 100644 --- a/packages/rfw/lib/src/dart/model.dart +++ b/packages/rfw/lib/src/dart/model.dart @@ -439,6 +439,28 @@ class ConstructorCall extends BlobNode { String toString() => '$name($arguments)'; } +/// Representation of functions that return widgets in Remote Flutter Widgets library blobs. +class WidgetBuilderDeclaration extends BlobNode { + /// Represents a callback that takes a single argument [argumentName] and returns the [widget]. + const WidgetBuilderDeclaration(this.argumentName, this.widget); + + /// The callback single argument name. + /// + /// In `Builder(builder: (scope) => Container());`, [argumentName] is "scope". + final String argumentName; + + /// The widget that will be returned when the builder callback is called. + /// + /// This is usually a [ConstructorCall], but may be a [Switch] (so long as + /// that [Switch] resolves to a [ConstructorCall]. Other values (or a [Switch] + /// that does not resolve to a constructor call) will result in an + /// [ErrorWidget] being used. + final BlobNode widget; + + @override + String toString() => '($argumentName) => $widget'; +} + /// Base class for various kinds of references in the RFW data structures. abstract class Reference extends BlobNode { /// Abstract const constructor. This constructor enables subclasses to provide @@ -534,6 +556,31 @@ class DataReference extends Reference { String toString() => 'data.${parts.join(".")}'; } +/// Reference to the single argument of type [DynamicMap] passed into the widget builder. +/// +/// This class is used to represent references to a function argument. +/// In `(scope) => Container(width: scope.width)`, this represents "scope.width". +/// +/// See also: +/// +/// * [WidgetBuilderDeclaration], which represents a widget builder definition. +class WidgetBuilderArgReference extends Reference { + /// Wraps the given [argumentName] and [parts] as a [WidgetBuilderArgReference]. + /// + /// The parts must not be mutated after the object is created. + const WidgetBuilderArgReference(this.argumentName, super.parts); + + /// A reference to a [WidgetBuilderDeclaration.argumentName]. + /// + /// In `Builder(builder: (scope) => Text(text: scope.result.text));`, + /// "scope.result.text" is the [WidgetBuilderArgReference]. + /// The [argumentName] is "scope" and its [parts] are `["result", "text"]`. + final String argumentName; + + @override + String toString() => '$argumentName.${parts.join('.')}'; +} + /// Unbound reference to a [Loop]. class LoopReference extends Reference { /// Wraps the given [loop] and [parts] as a [LoopReference]. diff --git a/packages/rfw/lib/src/dart/text.dart b/packages/rfw/lib/src/dart/text.dart index 88e0843ef3dd..4db42dcfbe0d 100644 --- a/packages/rfw/lib/src/dart/text.dart +++ b/packages/rfw/lib/src/dart/text.dart @@ -272,8 +272,8 @@ DynamicMap parseDataFile(String file) { /// declaration, along with its arguments. Arguments are a map of key-value /// pairs, where the values can be any of the types in the data model defined /// above plus any of the types defined below in this section, such as -/// references to arguments, the data model, loops, state, switches, or -/// event handlers. +/// references to arguments, the data model, widget builders, loops, state, +/// switches or event handlers. /// /// In this example, several constructor calls are nested together: /// @@ -283,6 +283,9 @@ DynamicMap parseDataFile(String file) { /// Container( /// child: Text(text: "Hello"), /// ), +/// Builder( +/// builder: (scope) => Text(text: scope.world), +/// ), /// ], /// ); /// ``` @@ -293,6 +296,35 @@ DynamicMap parseDataFile(String file) { /// constructor call also has only one argument, `child`, whose value, again, is /// a constructor call, in this case creating a `Text` widget. /// +/// ### Widget Builders +/// +/// Widget builders take a single argument and return a widget. +/// The [DynamicMap] argument consists of key-value pairs where values +/// can be of any types in the data model. Widget builders arguments are lexically +/// scoped so a given constructor call has access to any arguments where it is +/// defined plus arguments defined by its parents (if any). +/// +/// In this example several widget builders are nested together: +/// +/// ``` +/// widget Foo {text: 'this is cool'} = Builder( +/// builder: (foo) => Builder( +/// builder: (bar) => Builder( +/// builder: (baz) => Text( +/// text: [ +/// args.text, +/// state.text, +/// data.text, +/// foo.text, +/// bar.text, +/// baz.text, +/// ], +/// ), +/// ), +/// ), +/// ); +/// ``` +/// /// ### References /// /// Remote widget libraries typically contain _references_, e.g. to the @@ -610,6 +642,12 @@ const Set _reservedWords = { 'true', }; +void _checkIsNotReservedWord(String identifier, _Token identifierToken) { + if (_reservedWords.contains(identifier)) { + throw ParserException._fromToken('$identifier is a reserved word', identifierToken); + } +} + sealed class _Token { _Token(this.line, this.column, this.start, this.end); final int line; @@ -630,6 +668,7 @@ class _SymbolToken extends _Token { static const int colon = 0x3A; // U+003A COLON character (:) static const int semicolon = 0x3B; // U+003B SEMICOLON character (;) static const int equals = 0x3D; // U+003D EQUALS SIGN character (=) + static const int greatherThan = 0x3E; // U+003D GREATHER THAN character (>) static const int openBracket = 0x5B; // U+005B LEFT SQUARE BRACKET character ([) static const int closeBracket = 0x5D; // U+005D RIGHT SQUARE BRACKET character (]) static const int openBrace = 0x7B; // U+007B LEFT CURLY BRACKET character ({) @@ -812,6 +851,7 @@ Iterable<_Token> _tokenize(String file) sync* { case 0x3A: // U+003A COLON character (:) case 0x3B: // U+003B SEMICOLON character (;) case 0x3D: // U+003D EQUALS SIGN character (=) + case 0x3E: // U+003E GREATHER THAN SIGN character (>) case 0x5B: // U+005B LEFT SQUARE BRACKET character ([) case 0x5D: // U+005D RIGHT SQUARE BRACKET character (]) case 0x7B: // U+007B LEFT CURLY BRACKET character ({) @@ -2132,14 +2172,23 @@ class _Parser { return _readString(); } - DynamicMap _readMap({ required bool extended }) { + DynamicMap _readMap({ + required bool extended, + List widgetBuilderScope = const [], + }) { _expectSymbol(_SymbolToken.openBrace); - final DynamicMap results = _readMapBody(extended: extended); + final DynamicMap results = _readMapBody( + widgetBuilderScope: widgetBuilderScope, + extended: extended, + ); _expectSymbol(_SymbolToken.closeBrace); return results; } - DynamicMap _readMapBody({ required bool extended }) { + DynamicMap _readMapBody({ + required bool extended, + List widgetBuilderScope = const [], + }) { final DynamicMap results = DynamicMap(); // ignore: prefer_collection_literals while (_source.current is! _SymbolToken) { final String key = _readKey(); @@ -2147,7 +2196,11 @@ class _Parser { throw ParserException._fromToken('Duplicate key "$key" in map', _source.current); } _expectSymbol(_SymbolToken.colon); - final Object value = _readValue(extended: extended, nullOk: true); + final Object value = _readValue( + extended: extended, + nullOk: true, + widgetBuilderScope: widgetBuilderScope, + ); if (value != missing) { results[key] = value; } @@ -2162,7 +2215,10 @@ class _Parser { final List _loopIdentifiers = []; - DynamicList _readList({ required bool extended }) { + DynamicList _readList({ + required bool extended, + List widgetBuilderScope = const [], + }) { final DynamicList results = DynamicList.empty(growable: true); _expectSymbol(_SymbolToken.openBracket); while (!_foundSymbol(_SymbolToken.closeBracket)) { @@ -2172,19 +2228,26 @@ class _Parser { _expectIdentifier('for'); final _Token loopIdentifierToken = _source.current; final String loopIdentifier = _readIdentifier(); - if (_reservedWords.contains(loopIdentifier)) { - throw ParserException._fromToken('$loopIdentifier is a reserved word', loopIdentifierToken); - } + _checkIsNotReservedWord(loopIdentifier, loopIdentifierToken); _expectIdentifier('in'); - final Object collection = _readValue(extended: true); + final Object collection = _readValue( + widgetBuilderScope: widgetBuilderScope, + extended: true, + ); _expectSymbol(_SymbolToken.colon); _loopIdentifiers.add(loopIdentifier); - final Object template = _readValue(extended: extended); + final Object template = _readValue( + widgetBuilderScope: widgetBuilderScope, + extended: extended, + ); assert(_loopIdentifiers.last == loopIdentifier); _loopIdentifiers.removeLast(); results.add(_withSourceRange(Loop(collection, template), start)); } else { - final Object value = _readValue(extended: extended); + final Object value = _readValue( + widgetBuilderScope: widgetBuilderScope, + extended: extended, + ); results.add(value); } if (_foundSymbol(_SymbolToken.comma)) { @@ -2197,8 +2260,10 @@ class _Parser { return results; } - Switch _readSwitch(SourceLocation? start) { - final Object value = _readValue(extended: true); + Switch _readSwitch(SourceLocation? start, { + List widgetBuilderScope = const [], + }) { + final Object value = _readValue(extended: true, widgetBuilderScope: widgetBuilderScope); final Map cases = {}; _expectSymbol(_SymbolToken.openBrace); while (_source.current is! _SymbolToken) { @@ -2210,13 +2275,13 @@ class _Parser { key = null; _advance(); } else { - key = _readValue(extended: true); + key = _readValue(extended: true, widgetBuilderScope: widgetBuilderScope); if (cases.containsKey(key)) { throw ParserException._fromToken('Switch has duplicate cases for key $key', _source.current); } } _expectSymbol(_SymbolToken.colon); - final Object value = _readValue(extended: true); + final Object value = _readValue(extended: true, widgetBuilderScope: widgetBuilderScope); cases[key] = value; if (_foundSymbol(_SymbolToken.comma)) { _advance(); @@ -2249,13 +2314,19 @@ class _Parser { return results; } - Object _readValue({ required bool extended, bool nullOk = false }) { + Object _readValue({ + required bool extended, + bool nullOk = false, + List widgetBuilderScope = const [], + }) { if (_source.current is _SymbolToken) { switch ((_source.current as _SymbolToken).symbol) { case _SymbolToken.openBracket: - return _readList(extended: extended); + return _readList(widgetBuilderScope: widgetBuilderScope, extended: extended); case _SymbolToken.openBrace: - return _readMap(extended: extended); + return _readMap(widgetBuilderScope: widgetBuilderScope, extended: extended); + case _SymbolToken.openParen: + return _readWidgetBuilderDeclaration(widgetBuilderScope: widgetBuilderScope); } } else if (_source.current is _IntegerToken) { final Object result = (_source.current as _IntegerToken).value; @@ -2289,7 +2360,13 @@ class _Parser { if (identifier == 'event') { final SourceLocation? start = _getSourceLocation(); _advance(); - return _withSourceRange(EventHandler(_readString(), _readMap(extended: true)), start); + return _withSourceRange( + EventHandler( + _readString(), + _readMap(widgetBuilderScope: widgetBuilderScope, extended: true), + ), + start, + ); } if (identifier == 'args') { final SourceLocation? start = _getSourceLocation(); @@ -2309,7 +2386,7 @@ class _Parser { if (identifier == 'switch') { final SourceLocation? start = _getSourceLocation(); _advance(); - return _readSwitch(start); + return _readSwitch(start, widgetBuilderScope: widgetBuilderScope); } if (identifier == 'set') { final SourceLocation? start = _getSourceLocation(); @@ -2318,25 +2395,56 @@ class _Parser { _expectIdentifier('state'); final StateReference stateReference = _withSourceRange(StateReference(_readParts()), innerStart); _expectSymbol(_SymbolToken.equals); - final Object value = _readValue(extended: true); + final Object value = _readValue(widgetBuilderScope: widgetBuilderScope, extended: true); return _withSourceRange(SetStateHandler(stateReference, value), start); } + if (widgetBuilderScope.contains(identifier)) { + final SourceLocation? start = _getSourceLocation(); + _advance(); + return _withSourceRange(WidgetBuilderArgReference(identifier, _readParts()), start); + } final int index = _loopIdentifiers.lastIndexOf(identifier) + 1; if (index > 0) { final SourceLocation? start = _getSourceLocation(); _advance(); return _withSourceRange(LoopReference(_loopIdentifiers.length - index, _readParts(optional: true)), start); } - return _readConstructorCall(); + return _readConstructorCall(widgetBuilderScope: widgetBuilderScope); } throw ParserException._unexpected(_source.current); } - ConstructorCall _readConstructorCall() { + WidgetBuilderDeclaration _readWidgetBuilderDeclaration({ + List widgetBuilderScope = const [], + }) { + _expectSymbol(_SymbolToken.openParen); + final _Token argumentNameToken = _source.current; + final String argumentName = _readIdentifier(); + _checkIsNotReservedWord(argumentName, argumentNameToken); + _expectSymbol(_SymbolToken.closeParen); + _expectSymbol(_SymbolToken.equals); + _expectSymbol(_SymbolToken.greatherThan); + final _Token valueToken = _source.current; + final Object widget = _readValue( + extended: true, + widgetBuilderScope: [...widgetBuilderScope, argumentName], + ); + if (widget is! ConstructorCall && widget is! Switch) { + throw ParserException._fromToken('Expecting a switch or constructor call got $widget', valueToken); + } + return WidgetBuilderDeclaration(argumentName, widget as BlobNode); + } + + ConstructorCall _readConstructorCall({ + List widgetBuilderScope = const [], + }) { final SourceLocation? start = _getSourceLocation(); final String name = _readIdentifier(); _expectSymbol(_SymbolToken.openParen); - final DynamicMap arguments = _readMapBody(extended: true); + final DynamicMap arguments = _readMapBody( + extended: true, + widgetBuilderScope: widgetBuilderScope, + ); _expectSymbol(_SymbolToken.closeParen); return _withSourceRange(ConstructorCall(name, arguments), start); } diff --git a/packages/rfw/lib/src/flutter/core_widgets.dart b/packages/rfw/lib/src/flutter/core_widgets.dart index 379edfe5aa6c..a60e17f04073 100644 --- a/packages/rfw/lib/src/flutter/core_widgets.dart +++ b/packages/rfw/lib/src/flutter/core_widgets.dart @@ -277,7 +277,7 @@ Map get _coreWidgetsDefinitions => (Clip.values, source, ['clipBehavior']) ?? Clip.antiAlias, child: source.optionalChild(['child']), ); - }, + }, 'ColoredBox': (BuildContext context, DataSource source) { return ColoredBox( @@ -499,6 +499,7 @@ Map get _coreWidgetsDefinitions => (['opacity']) ?? 0.0, onEnd: source.voidHandler(['onEnd']), alwaysIncludeSemantics: source.v(['alwaysIncludeSemantics']) ?? true, + child: source.optionalChild(['child']), ); }, diff --git a/packages/rfw/lib/src/flutter/material_widgets.dart b/packages/rfw/lib/src/flutter/material_widgets.dart index cd1e515f2264..1ddd71ad7e65 100644 --- a/packages/rfw/lib/src/flutter/material_widgets.dart +++ b/packages/rfw/lib/src/flutter/material_widgets.dart @@ -30,9 +30,11 @@ import 'runtime.dart'; /// * [DropdownButton] /// * [ElevatedButton] /// * [FloatingActionButton] +/// * [InkResponse] /// * [InkWell] /// * [LinearProgressIndicator] /// * [ListTile] +/// * [Material] /// * [OutlinedButton] /// * [Scaffold] /// * [TextButton] @@ -337,14 +339,58 @@ Map get _materialWidgetsDefinitions => (TapDownDetails details) => trigger()), + onTapUp: source.handler(['onTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()), + onTapCancel: source.voidHandler(['onTapCancel']), + onDoubleTap: source.voidHandler(['onDoubleTap']), + onLongPress: source.voidHandler(['onLongPress']), + onSecondaryTap: source.voidHandler(['onSecondaryTap']), + onSecondaryTapUp: source.handler(['onSecondaryTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()), + onSecondaryTapDown: source.handler(['onSecondaryTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()), + onSecondaryTapCancel: source.voidHandler(['onSecondaryTapCancel']), + onHighlightChanged: source.handler(['onHighlightChanged'], (VoidCallback trigger) => (bool highlighted) => trigger()), + onHover: source.handler(['onHover'], (VoidCallback trigger) => (bool hovered) => trigger()), + containedInkWell: source.v(['containedInkWell']) ?? false, + highlightShape: ArgumentDecoders.enumValue(BoxShape.values, source, ['highlightShape']) ?? BoxShape.circle, + radius: source.v(['radius']), + borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)), + customBorder: ArgumentDecoders.shapeBorder(source, ['customBorder']), + focusColor: ArgumentDecoders.color(source, ['focusColor']), + hoverColor: ArgumentDecoders.color(source, ['hoverColor']), + highlightColor: ArgumentDecoders.color(source, ['highlightColor']), + splashColor: ArgumentDecoders.color(source, ['splashColor']), + enableFeedback: source.v(['enableFeedback']) ?? true, + excludeFromSemantics: source.v(['excludeFromSemantics']) ?? false, + canRequestFocus: source.v(['canRequestFocus']) ?? true, + onFocusChange: source.handler(['onFocusChange'], (VoidCallback trigger) => (bool focus) => trigger()), + autofocus: source.v(['autofocus']) ?? false, + hoverDuration: ArgumentDecoders.duration(source, ['hoverDuration'], context), + child: source.optionalChild(['child']), + ); + }, + 'InkWell': (BuildContext context, DataSource source) { - // not implemented: onHighlightChanged, onHover; mouseCursor; focusColor, hoverColor, highlightColor, overlayColor, splashColor; splashFactory; focusNode, onFocusChange + // not implemented: mouseCursor; overlayColor, splashFactory; focusNode, onFocusChange return InkWell( onTap: source.voidHandler(['onTap']), onDoubleTap: source.voidHandler(['onDoubleTap']), onLongPress: source.voidHandler(['onLongPress']), onTapDown: source.handler(['onTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()), onTapCancel: source.voidHandler(['onTapCancel']), + onSecondaryTap: source.voidHandler(['onSecondaryTap']), + onSecondaryTapUp: source.handler(['onSecondaryTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()), + onSecondaryTapDown: source.handler(['onSecondaryTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()), + onSecondaryTapCancel: source.voidHandler(['onSecondaryTapCancel']), + onHighlightChanged: source.handler(['onHighlightChanged'], (VoidCallback trigger) => (bool highlighted) => trigger()), + onHover: source.handler(['onHover'], (VoidCallback trigger) => (bool hovered) => trigger()), + focusColor: ArgumentDecoders.color(source, ['focusColor']), + hoverColor: ArgumentDecoders.color(source, ['hoverColor']), + highlightColor: ArgumentDecoders.color(source, ['highlightColor']), + splashColor: ArgumentDecoders.color(source, ['splashColor']), radius: source.v(['radius']), borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)), customBorder: ArgumentDecoders.shapeBorder(source, ['customBorder']), @@ -395,6 +441,23 @@ Map get _materialWidgetsDefinitions => (MaterialType.values,source, ['type']) ?? MaterialType.canvas, + elevation: source.v(['elevation']) ?? 0.0, + color: ArgumentDecoders.color(source, ['color']), + shadowColor: ArgumentDecoders.color(source, ['shadowColor']), + surfaceTintColor: ArgumentDecoders.color(source, ['surfaceTintColor']), + textStyle: ArgumentDecoders.textStyle(source, ['textStyle']), + borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius']), + shape: ArgumentDecoders.shapeBorder(source, ['shape']), + borderOnForeground: source.v(['borderOnForeground']) ?? true, + clipBehavior: ArgumentDecoders.enumValue(Clip.values, source, ['clipBehavior']) ?? Clip.none, + animationDuration: ArgumentDecoders.duration(source, ['animationDuration'], context), + child: source.child(['child']), + ); + }, + 'OutlinedButton': (BuildContext context, DataSource source) { // not implemented: buttonStyle, focusNode return OutlinedButton( diff --git a/packages/rfw/lib/src/flutter/runtime.dart b/packages/rfw/lib/src/flutter/runtime.dart index a610a762ce9d..732f345525ab 100644 --- a/packages/rfw/lib/src/flutter/runtime.dart +++ b/packages/rfw/lib/src/flutter/runtime.dart @@ -19,6 +19,9 @@ import 'content.dart'; /// [LocalWidgetBuilder] callbacks. typedef LocalWidgetBuilder = Widget Function(BuildContext context, DataSource source); +/// Signature of builders for remote widgets. +typedef _RemoteWidgetBuilder = _CurriedWidget Function(DynamicMap builderArg); + /// Signature of the callback passed to a [RemoteWidget]. /// /// This is used by [RemoteWidget] and [Runtime.build] as the callback for @@ -126,6 +129,25 @@ abstract class DataSource { /// non-widget nodes replaced by [ErrorWidget]. List childList(List argsKey); + /// Builds the widget builder at the given key. + /// + /// If the node is not a widget builder, returns an [ErrorWidget]. + /// + /// See also: + /// + /// * [optionalBuilder], which returns null if the widget builder is missing. + Widget builder(List argsKey, DynamicMap builderArg); + + /// Builds the widget builder at the given key. + /// + /// If the node is not a widget builder, returns null. + /// + /// See also: + /// + /// * [builder], which returns an [ErrorWidget] instead of null if the widget + /// builder is missing. + Widget? optionalBuilder(List argsKey, DynamicMap builderArg); + /// Gets a [VoidCallback] event handler at the given key. /// /// If the node specified is an [AnyEventHandler] or a [DynamicList] of @@ -249,12 +271,12 @@ class Runtime extends ChangeNotifier { /// /// The returned map is an immutable view of the map updated by calls to /// [update] and [clearLibraries]. - /// + /// /// The keys are instances [LibraryName] which encode fully qualified library /// names, and the values are the corresponding [WidgetLibrary]s. - /// + /// /// The returned map is an immutable copy of the registered libraries - /// at the time of this call. + /// at the time of this call. /// /// See also: /// @@ -284,11 +306,23 @@ class Runtime extends ChangeNotifier { /// /// The `remoteEventTarget` argument is the callback that the RFW runtime will /// invoke whenever a remote widget event handler is triggered. - Widget build(BuildContext context, FullyQualifiedWidgetName widget, DynamicContent data, RemoteEventHandler remoteEventTarget) { + Widget build( + BuildContext context, + FullyQualifiedWidgetName widget, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + ) { _CurriedWidget? boundWidget = _widgets[widget]; if (boundWidget == null) { _checkForImportLoops(widget.library); - boundWidget = _applyConstructorAndBindArguments(widget, const {}, -1, {}, null); + boundWidget = _applyConstructorAndBindArguments( + widget, + const {}, + const {}, + -1, + {}, + null, + ); _widgets[widget] = boundWidget; } return boundWidget.build(context, data, remoteEventTarget, const <_WidgetState>[]); @@ -410,13 +444,22 @@ class Runtime extends ChangeNotifier { /// [LocalWidgetBuilder] rather than a [WidgetDeclaration], and is used to /// provide source information for local widgets (which otherwise could not be /// associated with a part of the source). See also [Runtime.blobNodeFor]. - _CurriedWidget _applyConstructorAndBindArguments(FullyQualifiedWidgetName fullName, DynamicMap arguments, int stateDepth, Set usedWidgets, BlobNode? source) { + _CurriedWidget _applyConstructorAndBindArguments( + FullyQualifiedWidgetName fullName, + DynamicMap arguments, + DynamicMap widgetBuilderScope, + int stateDepth, + Set usedWidgets, + BlobNode? source, + ) { final _ResolvedConstructor? widget = _findConstructor(fullName); if (widget != null) { if (widget.constructor is WidgetDeclaration) { if (usedWidgets.contains(widget.fullName)) { - return _CurriedLocalWidget.error(fullName, 'Widget loop: Tried to call ${widget.fullName} constructor reentrantly.') - ..propagateSource(source); + return _CurriedLocalWidget.error( + fullName, + 'Widget loop: Tried to call ${widget.fullName} constructor reentrantly.', + )..propagateSource(source); } usedWidgets = usedWidgets.toSet()..add(widget.fullName); final WidgetDeclaration constructor = widget.constructor as WidgetDeclaration; @@ -426,22 +469,43 @@ class Runtime extends ChangeNotifier { } else { newDepth = stateDepth; } - Object result = _bindArguments(widget.fullName, constructor.root, arguments, newDepth, usedWidgets); + Object result = _bindArguments( + widget.fullName, + constructor.root, + arguments, + widgetBuilderScope, + newDepth, + usedWidgets, + ); if (result is Switch) { - result = _CurriedSwitch(widget.fullName, result, arguments, constructor.initialState) - ..propagateSource(result); + result = _CurriedSwitch( + widget.fullName, + result, + arguments, + widgetBuilderScope, + constructor.initialState, + )..propagateSource(result); } else { result as _CurriedWidget; if (constructor.initialState != null) { - result = _CurriedRemoteWidget(widget.fullName, result, arguments, constructor.initialState) - ..propagateSource(result); + result = _CurriedRemoteWidget( + widget.fullName, + result, + arguments, + widgetBuilderScope, + constructor.initialState, + )..propagateSource(result); } } return result as _CurriedWidget; } assert(widget.constructor is LocalWidgetBuilder); - return _CurriedLocalWidget(widget.fullName, widget.constructor as LocalWidgetBuilder, arguments) - ..propagateSource(source); + return _CurriedLocalWidget( + widget.fullName, + widget.constructor as LocalWidgetBuilder, + arguments, + widgetBuilderScope, + )..propagateSource(source); } final Set missingLibraries = _findMissingLibraries(fullName.library).toSet(); if (missingLibraries.isNotEmpty) { @@ -455,37 +519,93 @@ class Runtime extends ChangeNotifier { ..propagateSource(source); } - Object _bindArguments(FullyQualifiedWidgetName context, Object node, Object arguments, int stateDepth, Set usedWidgets) { + Object _bindArguments( + FullyQualifiedWidgetName context, + Object node, Object arguments, + DynamicMap widgetBuilderScope, + int stateDepth, + Set usedWidgets, + ) { if (node is ConstructorCall) { - final DynamicMap subArguments = _bindArguments(context, node.arguments, arguments, stateDepth, usedWidgets) as DynamicMap; - return _applyConstructorAndBindArguments(FullyQualifiedWidgetName(context.library, node.name), subArguments, stateDepth, usedWidgets, node); + final DynamicMap subArguments = _bindArguments( + context, + node.arguments, + arguments, + widgetBuilderScope, + stateDepth, + usedWidgets, + ) as DynamicMap; + return _applyConstructorAndBindArguments( + FullyQualifiedWidgetName(context.library, node.name), + subArguments, + widgetBuilderScope, + stateDepth, + usedWidgets, + node, + ); } + if (node is WidgetBuilderDeclaration) { + return (DynamicMap widgetBuilderArg) { + final DynamicMap newWidgetBuilderScope = { + ...widgetBuilderScope, + node.argumentName: widgetBuilderArg, + }; + final Object result = _bindArguments( + context, + node.widget, + arguments, + newWidgetBuilderScope, + stateDepth, + usedWidgets, + ); + if (result is Switch) { + return _CurriedSwitch( + FullyQualifiedWidgetName(context.library, ''), + result, + arguments as DynamicMap, + newWidgetBuilderScope, + const {}, + )..propagateSource(result); + } + return result as _CurriedWidget; + }; + } if (node is DynamicMap) { return node.map( - (String name, Object? value) => MapEntry(name, _bindArguments(context, value!, arguments, stateDepth, usedWidgets)), + (String name, Object? value) => MapEntry( + name, + _bindArguments(context, value!, arguments, widgetBuilderScope, stateDepth, usedWidgets), + ), ); } if (node is DynamicList) { return List.generate( node.length, - (int index) => _bindArguments(context, node[index]!, arguments, stateDepth, usedWidgets), + (int index) => _bindArguments( + context, + node[index]!, + arguments, + widgetBuilderScope, + stateDepth, + usedWidgets, + ), growable: false, ); } if (node is Loop) { - final Object input = _bindArguments(context, node.input, arguments, stateDepth, usedWidgets); - final Object output = _bindArguments(context, node.output, arguments, stateDepth, usedWidgets); + final Object input = _bindArguments(context, node.input, arguments, widgetBuilderScope, stateDepth, usedWidgets); + final Object output = _bindArguments(context, node.output, arguments, widgetBuilderScope, stateDepth, usedWidgets); return Loop(input, output) ..propagateSource(node); } if (node is Switch) { return Switch( - _bindArguments(context, node.input, arguments, stateDepth, usedWidgets), + _bindArguments(context, node.input, arguments, widgetBuilderScope, stateDepth, usedWidgets), node.outputs.map( (Object? key, Object value) { return MapEntry( - key == null ? key : _bindArguments(context, key, arguments, stateDepth, usedWidgets), - _bindArguments(context, value, arguments, stateDepth, usedWidgets), + key == null ? key : _bindArguments(context, key, arguments, widgetBuilderScope, stateDepth, usedWidgets), + _bindArguments(context, value, arguments, widgetBuilderScope, stateDepth, usedWidgets), ); }, ), @@ -498,14 +618,25 @@ class Runtime extends ChangeNotifier { return node.bind(stateDepth)..propagateSource(node); } if (node is EventHandler) { - return EventHandler(node.eventName, _bindArguments(context, node.eventArguments, arguments, stateDepth, usedWidgets) as DynamicMap) - ..propagateSource(node); + return EventHandler( + node.eventName, + _bindArguments( + context, + node.eventArguments, + arguments, + widgetBuilderScope, + stateDepth, + usedWidgets, + ) as DynamicMap, + )..propagateSource(node); } if (node is SetStateHandler) { assert(node.stateReference is StateReference); final BoundStateReference stateReference = (node.stateReference as StateReference).bind(stateDepth); - return SetStateHandler(stateReference, _bindArguments(context, node.value, arguments, stateDepth, usedWidgets)) - ..propagateSource(node); + return SetStateHandler( + stateReference, + _bindArguments(context, node.value, arguments, widgetBuilderScope, stateDepth, usedWidgets), + )..propagateSource(node); } assert(node is! WidgetDeclaration); return node; @@ -528,12 +659,19 @@ class _ResolvedDynamicList { typedef _DataResolverCallback = Object Function(List dataKey); typedef _StateResolverCallback = Object Function(List stateKey, int depth); +typedef _WidgetBuilderArgResolverCallback = Object Function(List argKey); abstract class _CurriedWidget extends BlobNode { - const _CurriedWidget(this.fullName, this.arguments, this.initialState); + const _CurriedWidget( + this.fullName, + this.arguments, + this.widgetBuilderScope, + this.initialState, + ); final FullyQualifiedWidgetName fullName; final DynamicMap arguments; + final DynamicMap widgetBuilderScope; final DynamicMap? initialState; static Object _bindLoopVariable(Object node, Object argument, int depth) { @@ -569,6 +707,7 @@ abstract class _CurriedWidget extends BlobNode { node.fullName, node.child, _bindLoopVariable(node.arguments, argument, depth) as DynamicMap, + _bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap, )..propagateSource(node); } if (node is _CurriedRemoteWidget) { @@ -576,6 +715,7 @@ abstract class _CurriedWidget extends BlobNode { node.fullName, _bindLoopVariable(node.child, argument, depth) as _CurriedWidget, _bindLoopVariable(node.arguments, argument, depth) as DynamicMap, + _bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap, node.initialState, )..propagateSource(node); } @@ -584,6 +724,7 @@ abstract class _CurriedWidget extends BlobNode { node.fullName, _bindLoopVariable(node.root, argument, depth) as Switch, _bindLoopVariable(node.arguments, argument, depth) as DynamicMap, + _bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap, node.initialState, )..propagateSource(node); } @@ -615,7 +756,13 @@ abstract class _CurriedWidget extends BlobNode { // // TODO(ianh): This really should have some sort of caching. Right now, evaluating a whole list // ends up being around O(N^2) since we have to walk the list from the start for every entry. - static _ResolvedDynamicList _listLookup(DynamicList list, int targetEffectiveIndex, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { + static _ResolvedDynamicList _listLookup( + DynamicList list, + int targetEffectiveIndex, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { int currentIndex = 0; // where we are in `list` (some entries of which might represent multiple values, because they are themselves loops) int effectiveIndex = 0; // where we are in the fully expanded list (the coordinate space in which we're aiming for `targetEffectiveIndex`) while ((effectiveIndex <= targetEffectiveIndex || targetEffectiveIndex < 0) && currentIndex < list.length) { @@ -624,22 +771,46 @@ abstract class _CurriedWidget extends BlobNode { Object inputList = node.input; while (inputList is! DynamicList) { if (inputList is BoundArgsReference) { - inputList = _resolveFrom(inputList.arguments, inputList.parts, stateResolver, dataResolver); + inputList = _resolveFrom( + inputList.arguments, + inputList.parts, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); } else if (inputList is DataReference) { inputList = dataResolver(inputList.parts); } else if (inputList is BoundStateReference) { inputList = stateResolver(inputList.parts, inputList.depth); } else if (inputList is BoundLoopReference) { - inputList = _resolveFrom(inputList.value, inputList.parts, stateResolver, dataResolver); + inputList = _resolveFrom( + inputList.value, + inputList.parts, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); } else if (inputList is Switch) { - inputList = _resolveFrom(inputList, const [], stateResolver, dataResolver); + inputList = _resolveFrom( + inputList, + const [], + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); } else { // e.g. it's a map or something else that isn't indexable inputList = DynamicList.empty(); } assert(inputList is! _ResolvedDynamicList); } - final _ResolvedDynamicList entry = _listLookup(inputList, targetEffectiveIndex >= 0 ? targetEffectiveIndex - effectiveIndex : -1, stateResolver, dataResolver); + final _ResolvedDynamicList entry = _listLookup( + inputList, + targetEffectiveIndex >= 0 ? targetEffectiveIndex - effectiveIndex : -1, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); if (entry.result != null) { final Object boundResult = _bindLoopVariable(node.output, entry.result!, 0); return _ResolvedDynamicList(null, boundResult, null); @@ -656,7 +827,13 @@ abstract class _CurriedWidget extends BlobNode { return _ResolvedDynamicList(list, null, effectiveIndex); } - static Object _resolveFrom(Object root, List parts, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { + static Object _resolveFrom( + Object root, + List parts, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { int index = 0; Object current = root; while (true) { @@ -667,6 +844,9 @@ abstract class _CurriedWidget extends BlobNode { } current = dataResolver(current.parts); continue; + } else if (current is WidgetBuilderArgReference) { + current = widgetBuilderArgResolver([current.argumentName, ...current.parts]); + continue; } else if (current is BoundArgsReference) { List nextParts = current.parts; if (index < parts.length) { @@ -693,7 +873,13 @@ abstract class _CurriedWidget extends BlobNode { index = 0; continue; } else if (current is Switch) { - final Object key = _resolveFrom(current.input, const [], stateResolver, dataResolver); + final Object key = _resolveFrom( + current.input, + const [], + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); Object? value = current.outputs[key]; if (value == null) { value = current.outputs[null]; @@ -707,9 +893,15 @@ abstract class _CurriedWidget extends BlobNode { // We've reached the end of the line. // We handle some special leaf cases that still need processing before we return. if (current is EventHandler) { - current = EventHandler(current.eventName, _fix(current.eventArguments, stateResolver, dataResolver) as DynamicMap); + current = EventHandler( + current.eventName, + _fix(current.eventArguments, stateResolver, dataResolver, widgetBuilderArgResolver) as DynamicMap, + ); } else if (current is SetStateHandler) { - current = SetStateHandler(current.stateReference, _fix(current.value, stateResolver, dataResolver)); + current = SetStateHandler( + current.stateReference, + _fix(current.value, stateResolver, dataResolver, widgetBuilderArgResolver), + ); } // else `current` is nothing special, and we'll just return it below. break; // This is where the loop ends. @@ -725,7 +917,13 @@ abstract class _CurriedWidget extends BlobNode { if (parts[index] is! int) { return missing; } - current = _listLookup(current, parts[index] as int, stateResolver, dataResolver).result ?? missing; + current = _listLookup( + current, + parts[index] as int, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ).result ?? missing; } else { assert(current is! ArgsReference); assert(current is! StateReference); @@ -740,27 +938,60 @@ abstract class _CurriedWidget extends BlobNode { return current; } - static Object _fix(Object root, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { + static Object _fix( + Object root, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { if (root is DynamicMap) { - return root.map((String key, Object? value) => MapEntry(key, _fix(root[key]!, stateResolver, dataResolver))); + return root.map((String key, Object? value) => + MapEntry( + key, + _fix(root[key]!, stateResolver, dataResolver, widgetBuilderArgResolver), + ), + ); } else if (root is DynamicList) { if (root.any((Object? entry) => entry is Loop)) { - final int length = _listLookup(root, -1, stateResolver, dataResolver).length!; - return DynamicList.generate(length, (int index) => _fix(_listLookup(root, index, stateResolver, dataResolver).result!, stateResolver, dataResolver)); + final int length = _listLookup( + root, + -1, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ).length!; + return DynamicList.generate( + length, + (int index) => _fix( + _listLookup(root, index, stateResolver, dataResolver, widgetBuilderArgResolver).result!, + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ), + ); } else { - return DynamicList.generate(root.length, (int index) => _fix(root[index]!, stateResolver, dataResolver)); + return DynamicList.generate( + root.length, + (int index) => _fix(root[index]!, stateResolver, dataResolver, widgetBuilderArgResolver), + ); } } else if (root is BlobNode) { - return _resolveFrom(root, const [], stateResolver, dataResolver); + return _resolveFrom(root, const [], stateResolver, dataResolver, widgetBuilderArgResolver); } else { return root; } } - Object resolve(List parts, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver, { required bool expandLists }) { - Object result = _resolveFrom(arguments, parts, stateResolver, dataResolver); + Object resolve( + List parts, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, { + required bool expandLists, + }) { + Object result = _resolveFrom(arguments, parts, stateResolver, dataResolver, widgetBuilderArgResolver); if (result is DynamicList && expandLists) { - result = _listLookup(result, -1, stateResolver, dataResolver); + result = _listLookup(result, -1, stateResolver, dataResolver, widgetBuilderArgResolver); } assert(result is! Reference); assert(result is! Switch); @@ -768,38 +999,92 @@ abstract class _CurriedWidget extends BlobNode { return result; } - Widget build(BuildContext context, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states) { - return _Widget(curriedWidget: this, data: data, remoteEventTarget: remoteEventTarget, states: states); + Widget build( + BuildContext context, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + List<_WidgetState> states, + ) { + return _Widget( + curriedWidget: this, + data: data, + widgetBuilderScope: DynamicContent(widgetBuilderScope), + remoteEventTarget: remoteEventTarget, + states: states, + ); } - Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver); + Widget buildChild( + BuildContext context, + DataSource source, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + List<_WidgetState> states, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ); @override String toString() => '$fullName ${initialState ?? "{}"} $arguments'; } class _CurriedLocalWidget extends _CurriedWidget { - const _CurriedLocalWidget(FullyQualifiedWidgetName fullName, this.child, DynamicMap arguments) : super(fullName, arguments, null); + const _CurriedLocalWidget( + FullyQualifiedWidgetName fullName, + this.child, + DynamicMap arguments, + DynamicMap widgetBuilderScope, + ) : super(fullName, arguments, widgetBuilderScope, null); factory _CurriedLocalWidget.error(FullyQualifiedWidgetName fullName, String message) { - return _CurriedLocalWidget(fullName, (BuildContext context, DataSource data) => _buildErrorWidget(message), const {}); + return _CurriedLocalWidget( + fullName, + (BuildContext context, DataSource data) => _buildErrorWidget(message), + const {}, + const {}, + ); } final LocalWidgetBuilder child; @override - Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { + Widget buildChild( + BuildContext context, + DataSource source, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + List<_WidgetState> states, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { return child(context, source); } } class _CurriedRemoteWidget extends _CurriedWidget { - const _CurriedRemoteWidget(FullyQualifiedWidgetName fullName, this.child, DynamicMap arguments, DynamicMap? initialState) : super(fullName, arguments, initialState); + const _CurriedRemoteWidget( + FullyQualifiedWidgetName fullName, + this.child, + DynamicMap arguments, + DynamicMap widgetBuilderScope, + DynamicMap? initialState, + ) : super(fullName, arguments, widgetBuilderScope, initialState); final _CurriedWidget child; @override - Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { + Widget buildChild( + BuildContext context, + DataSource source, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + List<_WidgetState> states, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { return child.build(context, data, remoteEventTarget, states); } @@ -808,13 +1093,34 @@ class _CurriedRemoteWidget extends _CurriedWidget { } class _CurriedSwitch extends _CurriedWidget { - const _CurriedSwitch(FullyQualifiedWidgetName fullName, this.root, DynamicMap arguments, DynamicMap? initialState) : super(fullName, arguments, initialState); + const _CurriedSwitch( + FullyQualifiedWidgetName fullName, + this.root, + DynamicMap arguments, + DynamicMap widgetBuilderScope, + DynamicMap? initialState, + ) : super(fullName, arguments, widgetBuilderScope, initialState); final Switch root; @override - Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) { - final Object resolvedWidget = _CurriedWidget._resolveFrom(root, const [], stateResolver, dataResolver); + Widget buildChild( + BuildContext context, + DataSource source, + DynamicContent data, + RemoteEventHandler remoteEventTarget, + List<_WidgetState> states, + _StateResolverCallback stateResolver, + _DataResolverCallback dataResolver, + _WidgetBuilderArgResolverCallback widgetBuilderArgResolver, + ) { + final Object resolvedWidget = _CurriedWidget._resolveFrom( + root, + const [], + stateResolver, + dataResolver, + widgetBuilderArgResolver, + ); if (resolvedWidget is _CurriedWidget) { return resolvedWidget.build(context, data, remoteEventTarget, states); } @@ -826,12 +1132,20 @@ class _CurriedSwitch extends _CurriedWidget { } class _Widget extends StatefulWidget { - const _Widget({ required this.curriedWidget, required this.data, required this.remoteEventTarget, required this.states }); + const _Widget({ + required this.curriedWidget, + required this.data, + required this.widgetBuilderScope, + required this.remoteEventTarget, + required this.states, + }); final _CurriedWidget curriedWidget; final DynamicContent data; + final DynamicContent widgetBuilderScope; + final RemoteEventHandler remoteEventTarget; final List<_WidgetState> states; @@ -1014,6 +1328,36 @@ class _WidgetState extends State<_Widget> implements DataSource { ]; } + @override + Widget builder(List argsKey, DynamicMap builderArg) { + return _fetchBuilder(argsKey, builderArg, optional: false)!; + } + + @override + Widget? optionalBuilder(List argsKey, DynamicMap builderArg) { + return _fetchBuilder(argsKey, builderArg); + } + + Widget? _fetchBuilder( + List argsKey, + DynamicMap builderArg, { + bool optional = true, + }) { + final Object value = _fetch(argsKey, expandLists: false); + if (value is _RemoteWidgetBuilder) { + final _CurriedWidget curriedWidget = value(builderArg); + return curriedWidget.build( + context, + widget.data, + widget.remoteEventTarget, + widget.states, + ); + } + return optional + ? null + : _buildErrorWidget('Not a builder at $argsKey (got $value) for ${widget.curriedWidget.fullName}.'); + } + @override VoidCallback? voidHandler(List argsKey, [ DynamicMap? extraArguments ]) { return handler(argsKey, (HandlerTrigger callback) => () => callback(extraArguments)); @@ -1064,7 +1408,13 @@ class _WidgetState extends State<_Widget> implements DataSource { assert(!_debugFetching); try { _debugFetching = true; - final Object result = widget.curriedWidget.resolve(argsKey, _stateResolver, _dataResolver, expandLists: expandLists); + final Object result = widget.curriedWidget.resolve( + argsKey, + _stateResolver, + _dataResolver, + _widgetBuilderArgResolver, + expandLists: expandLists, + ); for (final _Subscription subscription in _dependencies) { subscription.addClient(key); } @@ -1095,6 +1445,17 @@ class _WidgetState extends State<_Widget> implements DataSource { return subscription.value; } + Object _widgetBuilderArgResolver(List rawDataKey) { + final _Key widgetBuilderArgKey = _Key(_kWidgetBuilderArgSection, rawDataKey); + final _Subscription subscription = _subscriptions[widgetBuilderArgKey] ??= _Subscription( + widget.widgetBuilderScope, + this, + rawDataKey, + ); + _dependencies.add(subscription); + return subscription.value; + } + Object _stateResolver(List rawStateKey, int depth) { final _Key stateKey = _Key(depth, rawStateKey); final _Subscription subscription; @@ -1126,7 +1487,16 @@ class _WidgetState extends State<_Widget> implements DataSource { @override Widget build(BuildContext context) { // TODO(ianh): what if this creates some _dependencies? - return widget.curriedWidget.buildChild(context, this, widget.data, widget.remoteEventTarget, _states, _stateResolver, _dataResolver); + return widget.curriedWidget.buildChild( + context, + this, + widget.data, + widget.remoteEventTarget, + _states, + _stateResolver, + _dataResolver, + _widgetBuilderArgResolver, + ); } @override @@ -1138,6 +1508,7 @@ class _WidgetState extends State<_Widget> implements DataSource { const int _kDataSection = -1; const int _kArgsSection = -2; +const int _kWidgetBuilderArgSection = -3; @immutable class _Key { @@ -1188,11 +1559,12 @@ class _Subscription { } } -ErrorWidget _buildErrorWidget(String message) { - FlutterError.reportError(FlutterErrorDetails( +Widget _buildErrorWidget(String message) { + final FlutterErrorDetails detail = FlutterErrorDetails( exception: message, stack: StackTrace.current, library: 'Remote Flutter Widgets', - )); - return ErrorWidget(message); + ); + FlutterError.reportError(detail); + return ErrorWidget.builder(detail); } diff --git a/packages/rfw/pubspec.yaml b/packages/rfw/pubspec.yaml index 9dd0ddc38c91..a83be027aafe 100644 --- a/packages/rfw/pubspec.yaml +++ b/packages/rfw/pubspec.yaml @@ -2,7 +2,7 @@ name: rfw description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime." repository: https://github.com/flutter/packages/tree/main/packages/rfw issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22 -version: 1.0.23 +version: 1.0.26 environment: sdk: ^3.2.0 diff --git a/packages/rfw/test/binary_test.dart b/packages/rfw/test/binary_test.dart index 0eca7888bbe9..916f92bf15e5 100644 --- a/packages/rfw/test/binary_test.dart +++ b/packages/rfw/test/binary_test.dart @@ -503,4 +503,41 @@ void main() { expect((value.widgets.first.root as ConstructorCall).name, 'c'); expect((value.widgets.first.root as ConstructorCall).arguments, isEmpty); }); + + testWidgets('Library encoder: widget builders work', (WidgetTester tester) async { + const String source = ''' + widget Foo = Builder( + builder: (scope) => Text(text: scope.text), + ); + '''; + final RemoteWidgetLibrary library = parseLibraryFile(source); + final Uint8List encoded = encodeLibraryBlob(library); + final RemoteWidgetLibrary decoded = decodeLibraryBlob(encoded); + + expect(library.toString(), decoded.toString()); + }); + + testWidgets('Library encoder: widget builders throws', (WidgetTester tester) async { + const RemoteWidgetLibrary remoteWidgetLibrary = RemoteWidgetLibrary( + [], + [ + WidgetDeclaration( + 'a', + {}, + ConstructorCall( + 'c', + { + 'builder': WidgetBuilderDeclaration('scope', ArgsReference([])), + }, + ), + ), + ], + ); + try { + decodeLibraryBlob(encodeLibraryBlob(remoteWidgetLibrary)); + fail('did not throw exception'); + } on FormatException catch (e) { + expect('$e', contains('Unrecognized data type 0x0A while decoding widget builder blob.')); + } + }); } diff --git a/packages/rfw/test/core_widgets_test.dart b/packages/rfw/test/core_widgets_test.dart index 40521a667455..75f5141c1c1e 100644 --- a/packages/rfw/test/core_widgets_test.dart +++ b/packages/rfw/test/core_widgets_test.dart @@ -106,7 +106,10 @@ void main() { runtime.update(const LibraryName(['test']), parseLibraryFile(''' import core; - widget root = Opacity(onEnd: event 'end' {}); + widget root = Opacity( + onEnd: event 'end' {}, + child: Placeholder(), + ); ''')); await tester.pump(); expect(tester.widget(find.byType(AnimatedOpacity)).onEnd, isNot(isNull)); @@ -226,7 +229,10 @@ void main() { child: FractionallySizedBox( widthFactor: 0.5, heightFactor: 0.8, - child: Text(text: "test"), + child: Text( + text: "test", + textScaleFactor: 3.0, + ), ), ); ''')); @@ -235,6 +241,7 @@ void main() { final Size childSize = tester.getSize(find.text('test')); expect(childSize.width, fractionallySizedBoxSize.width * 0.5); expect(childSize.height, fractionallySizedBoxSize.height * 0.8); + expect(tester.widget(find.text('test')).textScaler, const TextScaler.linear(3)); expect(tester.widget(find.byType(FractionallySizedBox)).alignment, Alignment.center); }); @@ -286,7 +293,7 @@ void main() { ''')); await tester.pump(); expect(find.byType(ClipRRect), findsOneWidget); - final RenderClipRRect renderClip = tester.allRenderObjects.whereType().first; + final RenderClipRRect renderClip = tester.allRenderObjects.whereType().first; expect(renderClip.clipBehavior, equals(Clip.antiAlias)); expect(renderClip.borderRadius, equals(BorderRadius.zero)); }); diff --git a/packages/rfw/test/goldens/material_test.ink_response_hover.png b/packages/rfw/test/goldens/material_test.ink_response_hover.png new file mode 100644 index 000000000000..3156e657805b Binary files /dev/null and b/packages/rfw/test/goldens/material_test.ink_response_hover.png differ diff --git a/packages/rfw/test/goldens/material_test.ink_response_tap.png b/packages/rfw/test/goldens/material_test.ink_response_tap.png new file mode 100644 index 000000000000..ea22b4cd4a30 Binary files /dev/null and b/packages/rfw/test/goldens/material_test.ink_response_tap.png differ diff --git a/packages/rfw/test/goldens/material_test.material_properties.png b/packages/rfw/test/goldens/material_test.material_properties.png new file mode 100644 index 000000000000..c7de8515b4c6 Binary files /dev/null and b/packages/rfw/test/goldens/material_test.material_properties.png differ diff --git a/packages/rfw/test/material_widgets_test.dart b/packages/rfw/test/material_widgets_test.dart index ac397ec54542..56da9abfd92b 100644 --- a/packages/rfw/test/material_widgets_test.dart +++ b/packages/rfw/test/material_widgets_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:rfw/formats.dart' show parseLibraryFile; @@ -438,4 +439,151 @@ void main() { skip: !runGoldens, ); }); + + testWidgets('Implement InkResponse properties', (WidgetTester tester) async { + final Runtime runtime = setupRuntime(); + final DynamicContent data = DynamicContent(); + final List eventLog = []; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(testName, 'root'), + onEvent: (String eventName, DynamicMap eventArguments) { + eventLog.add('$eventName $eventArguments'); + }, + ), + ), + ); + expect( + tester.takeException().toString(), + contains('Could not find remote widget named'), + ); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + widget root = Scaffold( + body: Center( + child: InkResponse( + onTap: event 'onTap' {}, + onHover: event 'onHover' {}, + borderRadius: [{x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}], + hoverColor: 0xFF00FF00, + splashColor: 0xAA0000FF, + highlightColor: 0xAAFF0000, + containedInkWell: true, + highlightShape: 'circle', + child: Text(text: 'InkResponse'), + ), + ), + ); + ''')); + await tester.pump(); + + expect(find.byType(InkResponse), findsOneWidget); + + // Hover + final Offset center = tester.getCenter(find.byType(InkResponse)); + final TestGesture gesture = + await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile('goldens/material_test.ink_response_hover.png'), + skip: !runGoldens, + ); + expect(eventLog, contains('onHover {}')); + + // Tap + await gesture.down(center); + await tester.pump(); // start gesture + await tester.pump(const Duration( + milliseconds: 200)); // wait for splash to be well under way + + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile('goldens/material_test.ink_response_tap.png'), + skip: !runGoldens, + ); + await gesture.up(); + await tester.pump(); + + expect(eventLog, contains('onTap {}')); + }); + + testWidgets('Implement Material properties', (WidgetTester tester) async { + final Runtime runtime = setupRuntime(); + final DynamicContent data = DynamicContent(); + final List eventLog = []; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(testName, 'root'), + onEvent: (String eventName, DynamicMap eventArguments) { + eventLog.add('$eventName $eventArguments'); + }, + ), + ), + ); + expect( + tester.takeException().toString(), + contains('Could not find remote widget named'), + ); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + widget root = Material( + type: 'circle', + elevation: 6.0, + color: 0xFF0000FF, + shadowColor: 0xFF00FF00, + surfaceTintColor: 0xff0000ff, + animationDuration: 300, + borderOnForeground: false, + child: SizedBox( + width: 20.0, + height: 20.0, + ), + ); + ''')); + await tester.pump(); + + expect(tester.widget(find.byType(Material)).animationDuration, + const Duration(milliseconds: 300)); + expect(tester.widget(find.byType(Material)).borderOnForeground, + false); + await expectLater( + find.byType(RemoteWidget), + matchesGoldenFile('goldens/material_test.material_properties.png'), + skip: !runGoldens, + ); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + widget root = Material( + clipBehavior: 'antiAlias', + shape: { type: 'circle', side: { width: 10.0, color: 0xFF0066FF } }, + child: SizedBox( + width: 20.0, + height: 20.0, + ), + ); + ''')); + await tester.pump(); + + expect(tester.widget(find.byType(Material)).clipBehavior, + Clip.antiAlias); + }); } diff --git a/packages/rfw/test/runtime_test.dart b/packages/rfw/test/runtime_test.dart index 89cb96d62d51..05eeb05a1bac 100644 --- a/packages/rfw/test/runtime_test.dart +++ b/packages/rfw/test/runtime_test.dart @@ -244,7 +244,7 @@ void main() { ), ); expect(tester.takeException().toString(), contains('Could not find remote widget named')); - expect(tester.widget(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a, possibly because some dependencies were missing: b'); + expect(tester.widget(find.byType(ErrorWidget)).message, contains('Could not find remote widget named widget in a, possibly because some dependencies were missing: b')); }); testWidgets('Missing libraries in specified widget', (WidgetTester tester) async { @@ -258,7 +258,7 @@ void main() { ), ); expect(tester.takeException().toString(), contains('Could not find remote widget named')); - expect(tester.widget(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a, possibly because some dependencies were missing: a'); + expect(tester.widget(find.byType(ErrorWidget)).message, contains('Could not find remote widget named widget in a, possibly because some dependencies were missing: a')); }); testWidgets('Missing libraries in import via dependency', (WidgetTester tester) async { @@ -276,7 +276,7 @@ void main() { ), ); expect(tester.takeException().toString(), contains('Could not find remote widget named')); - expect(tester.widget(find.byType(ErrorWidget)).message, 'Could not find remote widget named test in a, possibly because some dependencies were missing: b'); + expect(tester.widget(find.byType(ErrorWidget)).message, contains('Could not find remote widget named test in a, possibly because some dependencies were missing: b')); }); testWidgets('Missing widget', (WidgetTester tester) async { @@ -291,7 +291,7 @@ void main() { ), ); expect(tester.takeException().toString(), contains('Could not find remote widget named')); - expect(tester.widget(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a.'); + expect(tester.widget(find.byType(ErrorWidget)).message, contains('Could not find remote widget named widget in a.')); }); testWidgets('Runtime', (WidgetTester tester) async { @@ -1088,4 +1088,492 @@ void main() { data.update('c', 'test'); expect(log, ['leaf: 2', 'root: {a: [2, 3], b: [q, r]}', 'root: {a: [2, 3], b: [q, r], c: test}']); }); + + testWidgets('Data source - optional builder works', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Builder': (BuildContext context, DataSource source) { + final Widget? builder = source.optionalBuilder(['builder'], {}); + return builder ?? const Text('Hello World!', textDirection: TextDirection.ltr); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = Builder( + builder: Text(text: 'Not a builder :/'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + + final Finder textFinder = find.byType(Text); + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'Hello World!'); + }); + + testWidgets('Data source - builder returns an error widget', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + const String expectedErrorMessage = 'Not a builder at [builder] (got core:Text {} {text: Not a builder :/}) for local:Builder.'; + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Builder': (BuildContext context, DataSource source) { + return source.builder(['builder'], {}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = Builder( + builder: Text(text: 'Not a builder :/'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(tester.takeException().toString(), contains(expectedErrorMessage)); + expect(find.byType(ErrorWidget), findsOneWidget); + expect(tester.widget(find.byType(ErrorWidget)).message, contains(expectedErrorMessage)); + }); + + + testWidgets('Customized error widget', (WidgetTester tester) async { + final ErrorWidgetBuilder oldBuilder = ErrorWidget.builder; + ErrorWidget.builder = (FlutterErrorDetails details) { + return const Text('oopsie!', textDirection: TextDirection.ltr); + }; + final Runtime runtime = Runtime() + ..update(const LibraryName(['a']), parseLibraryFile('')); + final DynamicContent data = DynamicContent(); + await tester.pumpWidget( + RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(LibraryName(['a']), 'widget'), + ), + ); + expect(tester.takeException().toString(), contains('Could not find remote widget named')); + expect(find.text('oopsie!'), findsOneWidget); + expect(find.byType(ErrorWidget), findsNothing); + ErrorWidget.builder = oldBuilder; + }); + + testWidgets('Widget builders - work when scope is not used', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Builder': (BuildContext context, DataSource source) { + return source.builder(['builder'], {}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = Builder( + builder: (scope) => Text(text: 'Hello World!', textDirection: 'ltr'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'Hello World!'); + }); + + testWidgets('Widget builders - work when scope is used', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'HelloWorld': (BuildContext context, DataSource source) { + const String result = 'Hello World!'; + return source.builder(['builder'], {'result': result}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = HelloWorld( + builder: (result) => Text(text: result.result, textDirection: 'ltr'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'Hello World!'); + }); + + testWidgets('Widget builders - work with state', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'IntToString': (BuildContext context, DataSource source) { + final int value = source.v(['value'])!; + final String result = value.toString(); + return source.builder(['builder'], {'result': result}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test {value: 0} = IntToString( + value: state.value, + builder: (result) => Text(text: result.result, textDirection: 'ltr'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, '0'); + }); + + + testWidgets('Widget builders - work with data', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent({'value': 0}); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'IntToString': (BuildContext context, DataSource source) { + final int value = source.v(['value'])!; + final String result = value.toString(); + return source.builder(['builder'], {'result': result}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = IntToString( + value: data.value, + builder: (result) => Text(text: result.result, textDirection: 'ltr'), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, '0'); + + data.update('value', 1); + await tester.pump(); + expect(tester.widget(textFinder).data, '1'); + }); + + testWidgets('Widget builders - work with events', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final List dispatchedEvents = []; + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Zero': (BuildContext context, DataSource source) { + return source.builder(['builder'], {'result': 0}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = Zero( + builder: (result) => GestureDetector( + onTap: event 'works' {number: result.result}, + child: Text(text: 'Tap to trigger an event.', textDirection: 'ltr'), + ), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + onEvent: (String eventName, DynamicMap eventArguments) => + dispatchedEvents.add(RfwEvent(eventName, eventArguments)), + )); + + await tester.tap(textFinder); + await tester.pump(); + expect(dispatchedEvents, hasLength(1)); + expect(dispatchedEvents.single.name, 'works'); + expect(dispatchedEvents.single.arguments['number'], 0); + }); + + testWidgets('Widget builders - works nested', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Sum': (BuildContext context, DataSource source) { + final int operand1 = source.v(['operand1'])!; + final int operand2 = source.v(['operand2'])!; + final int result = operand1 + operand2; + return source.builder(['builder'], {'result': result}); + }, + 'IntToString': (BuildContext context, DataSource source) { + final int value = source.v(['value'])!; + final String result = value.toString(); + return source.builder(['builder'], {'result': result}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test = Sum( + operand1: 1, + operand2: 2, + builder: (result1) => IntToString( + value: result1.result, + builder: (result2) => Text(text: ['1 + 2 = ', result2.result], textDirection: 'ltr'), + ), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, '1 + 2 = 3'); + }); + + testWidgets('Widget builders - works nested dynamically', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Map handlers = {}; + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent({ + 'a1': 'apricot', + 'b1': 'blueberry', + }); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Builder': (BuildContext context, DataSource source) { + final String? id = source.v(['id']); + if (id != null) { + handlers[id] = source.voidHandler(['handler'])!; + } + return source.builder(['builder'], { + 'param1': source.v(['arg1']), + 'param2': source.v(['arg2']), + }); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test { state1: 'strawberry' } = Builder( + arg1: data.a1, + arg2: 'apple', + id: 'A', + handler: set state.state1 = 'STRAWBERRY', + builder: (builder1) => Builder( + arg1: data.b1, + arg2: 'banana', + builder: (builder2) => Text( + textDirection: 'ltr', + text: [ + state.state1, ' ', builder1.param1, ' ', builder1.param2, ' ', builder2.param1, ' ', builder2.param2, + ], + ), + ), + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(tester.widget(textFinder).data, 'strawberry apricot apple blueberry banana'); + + data.update('a1', 'APRICOT'); + await tester.pump(); + expect(tester.widget(textFinder).data, 'strawberry APRICOT apple blueberry banana'); + + data.update('b1', 'BLUEBERRY'); + await tester.pump(); + expect(tester.widget(textFinder).data, 'strawberry APRICOT apple BLUEBERRY banana'); + + handlers['A']!(); + await tester.pump(); + expect(tester.widget(textFinder).data, 'STRAWBERRY APRICOT apple BLUEBERRY banana'); + }); + + testWidgets('Widget builders - switch works with builder', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Builder': (BuildContext context, DataSource source) { + return source.builder(['builder'], {}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test {enabled: false} = Builder( + value: state.value, + builder: switch state.enabled { + true: (scope) => GestureDetector( + onTap: set state.enabled = false, + child: Text(text: 'The builder is enabled.', textDirection: 'ltr'), + ), + false: (scope) => GestureDetector( + onTap: set state.enabled = true, + child: Text(text: 'The builder is disabled.', textDirection: 'ltr'), + ), + }, + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'The builder is disabled.'); + + await tester.tap(textFinder); + await tester.pump(); + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'The builder is enabled.'); + }); + + testWidgets('Widget builders - builder works with switch', (WidgetTester tester) async { + const LibraryName coreLibraryName = LibraryName(['core']); + const LibraryName localLibraryName = LibraryName(['local']); + const LibraryName remoteLibraryName = LibraryName(['remote']); + final Runtime runtime = Runtime(); + final DynamicContent data = DynamicContent(); + final Finder textFinder = find.byType(Text); + runtime.update(coreLibraryName, createCoreWidgets()); + runtime.update(localLibraryName, LocalWidgetLibrary( { + 'Inverter': (BuildContext context, DataSource source) { + final bool value = source.v(['value'])!; + return source.builder(['builder'], {'result': !value}); + }, + })); + runtime.update(remoteLibraryName, parseLibraryFile(''' + import core; + import local; + + widget test {value: false} = Inverter( + value: state.value, + builder: (result) => switch result.result { + true: GestureDetector( + onTap: set state.value = switch state.value { + true: false, + false: true, + }, + child: Text(text: 'The input is false, the output is true', textDirection: 'ltr'), + ), + false: GestureDetector( + onTap: set state.value = switch state.value { + true: false, + false: true, + }, + child: Text(text: 'The input is true, the output is false', textDirection: 'ltr'), + ), + }, + ); + ''')); + await tester.pumpWidget(RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(remoteLibraryName, 'test'), + )); + + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'The input is false, the output is true'); + + await tester.tap(textFinder); + await tester.pump(); + expect(textFinder, findsOneWidget); + expect(tester.widget(textFinder).data, 'The input is true, the output is false'); + }); +} + +final class RfwEvent { + RfwEvent(this.name, this.arguments); + + final String name; + final DynamicMap arguments; } diff --git a/packages/rfw/test/text_test.dart b/packages/rfw/test/text_test.dart index 8fe51d75a3d9..2dac169fb874 100644 --- a/packages/rfw/test/text_test.dart +++ b/packages/rfw/test/text_test.dart @@ -340,4 +340,145 @@ void main() { final RemoteWidgetLibrary result = parseLibraryFile('widget a {b: 0} = c();'); expect(result.widgets.single.initialState, {'b': 0}); }); + + testWidgets('parseLibraryFile: widgetBuilders work', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = Builder(builder: (scope) => Container()); + '''); + expect(libraryFile.toString(), 'widget a = Builder({builder: (scope) => Container({})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with arguments', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = Builder(builder: (scope) => Container(width: scope.width)); + '''); + expect(libraryFile.toString(), 'widget a = Builder({builder: (scope) => Container({width: scope.width})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilder arguments are lexical scoped', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + a: (s1) => B( + b: (s2) => T(s1: s1.s1, s2: s2.s2), + ), + ); + '''); + expect(libraryFile.toString(), 'widget a = A({a: (s1) => B({b: (s2) => T({s1: s1.s1, s2: s2.s2})})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilder arguments can be shadowed', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + a: (s1) => B( + b: (s1) => T(t: s1.foo), + ), + ); + '''); + expect(libraryFile.toString(), 'widget a = A({a: (s1) => B({b: (s1) => T({t: s1.foo})})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders check the returned value', (WidgetTester tester) async { + void test(String input, String expectedMessage) { + try { + parseLibraryFile(input); + fail('parsing `$input` did not result in an error (expected "$expectedMessage").'); + } on ParserException catch (e) { + expect('$e', expectedMessage); + } + } + + const String expectedErrorMessage = + 'Expecting a switch or constructor call got 1 at line 1 column 27.'; + test('widget a = B(b: (foo) => 1);', expectedErrorMessage); + }); + + testWidgets('parseLibraryFile: widgetBuilders check reserved words', (WidgetTester tester) async { + void test(String input, String expectedMessage) { + try { + parseLibraryFile(input); + fail('parsing `$input` did not result in an error (expected "$expectedMessage").'); + } on ParserException catch (e) { + expect('$e', expectedMessage); + } + } + + const String expectedErrorMessage = + 'args is a reserved word at line 1 column 34.'; + test('widget a = Builder(builder: (args) => Container(width: args.width));', expectedErrorMessage); + }); + + testWidgets('parseLibraryFile: widgetBuilders check reserved words', (WidgetTester tester) async { + void test(String input, String expectedMessage) { + try { + parseDataFile(input); + fail('parsing `$input` did not result in an error (expected "$expectedMessage").'); + } on ParserException catch (e) { + expect('$e', expectedMessage); + } + } + + const String expectedErrorMessage = + 'Expected symbol "{" but found widget at line 1 column 7.'; + test('widget a = Builder(builder: (args) => Container(width: args.width));', expectedErrorMessage); + }); + + testWidgets('parseLibraryFile: switch works with widgetBuilders', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + b: switch args.down { + true: (foo) => B(), + false: (bar) => C(), + } + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: switch args.down {true: (foo) => B({}), false: (bar) => C({})}});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with switch', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + b: (foo) => switch foo.letter { + 'a': A(), + 'b': B(), + }, + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: (foo) => switch foo.letter {a: A({}), b: B({})}});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with lists', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + b: (s1) => B(c: [s1.c]), + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: (s1) => B({c: [s1.c]})});' ); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with maps', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a = A( + b: (s1) => B(c: {d: s1.d}), + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: (s1) => B({c: {d: s1.d}})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with setters', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a {foo: 0} = A( + b: (s1) => B(onTap: set state.foo = s1.foo), + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: (s1) => B({onTap: set state.foo = s1.foo})});'); + }); + + testWidgets('parseLibraryFile: widgetBuilders work with events', (WidgetTester tester) async { + final RemoteWidgetLibrary libraryFile = parseLibraryFile(''' + widget a {foo: 0} = A( + b: (s1) => B(onTap: event "foo" {result: s1.result}) + ); + '''); + expect(libraryFile.toString(), 'widget a = A({b: (s1) => B({onTap: event foo {result: s1.result}})});'); + }); } diff --git a/packages/rfw/test_coverage/bin/test_coverage.dart b/packages/rfw/test_coverage/bin/test_coverage.dart index f5f0dcdc9cd4..6097086dae44 100644 --- a/packages/rfw/test_coverage/bin/test_coverage.dart +++ b/packages/rfw/test_coverage/bin/test_coverage.dart @@ -18,15 +18,15 @@ import 'package:meta/meta.dart'; // once it is loaded you can call `M-x coverlay-display-stats` to get a summary // of the files to look at.) -// Please update these targets when you update this package. -// Please ensure that test coverage continues to be 100%. -// Don't forget to update the lastUpdate date too! -const int targetLines = 3273; -const String targetPercent = '100'; -const String lastUpdate = '2024-01-30'; +// If Dart coverage increases the number of lines that could be covered, it is +// possible that this package will no longer report 100% coverage even though +// nothing has changed about the package itself. In the event that that happens, +// set this constant to the number of lines currently being covered and file a +// bug, cc'ing the current package owner (Hixie) so that they can add more tests. +const int? targetLines = null; @immutable -/* final */ class LcovLine { +final class LcovLine { const LcovLine(this.filename, this.line); final String filename; final int line; @@ -161,53 +161,42 @@ Future main(List arguments) async { final String coveredPercent = (100.0 * coveredLines / totalLines).toStringAsFixed(1); - // We only check the TARGET_LINES matches, not the TARGET_PERCENT, - // because we expect the percentage to drop over time as Dart fixes - // various bugs in how it determines what lines are coverable. - if (coveredLines < targetLines && targetLines <= totalLines) { + if (targetLines != null) { + if (targetLines! < totalLines) { + print( + 'Warning: test_coverage has an override set to reduce the expected number of covered lines from $totalLines to $targetLines.\n' + 'New tests should be written to cover all lines in the package.', + ); + totalLines = targetLines!; + } else if (targetLines == totalLines) { + print( + 'Warning: test_coverage has a redundant targetLines; it is equal to the actual number of coverable lines ($totalLines).\n' + 'Update test_coverage.dart to set the targetLines constant to null.', + ); + } else { + print( + 'Warning: test_coverage has an outdated targetLines ($targetLines) that is above the total number of lines in the package ($totalLines).\n' + 'Update test_coverage.dart to set the targetLines constant to null.', + ); + } + } + + if (coveredLines < totalLines) { print(''); print(' ╭──────────────────────────────╮'); print(' │ COVERAGE REGRESSION DETECTED │'); print(' ╰──────────────────────────────╯'); print(''); print( - 'Coverage has reduced to only $coveredLines lines ($coveredPercent%). This is lower than', - ); - print( - 'it was as of $lastUpdate, when coverage was $targetPercent%, covering $targetLines lines.', - ); - print( - 'Please add sufficient tests to get coverage back to 100%, and update', - ); - print( - 'test_coverage/bin/test_coverage.dart to have the appropriate targets.', + 'Coverage has reduced to only $coveredLines lines ($coveredPercent%), out\n' + 'of $totalLines total lines; ${totalLines - coveredLines} lines are not covered by tests.\n' + 'Please add sufficient tests to get coverage back to 100%.', ); print(''); print( 'When in doubt, ask @Hixie for advice. Thanks!', ); exit(1); - } else { - if (coveredLines < totalLines) { - print( - 'Warning: Coverage of package:rfw is no longer 100%. (Coverage is now $coveredPercent%, $coveredLines/$totalLines lines.)', - ); - } - if (coveredLines > targetLines) { - print( - 'Total lines of covered code has increased, and coverage script is now out of date.\n' - 'Coverage is now $coveredPercent%, $coveredLines/$totalLines lines, whereas previously there were only $targetLines lines.\n' - 'Update the "targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $coveredLines).', - ); - } - if (targetLines > totalLines) { - print( - 'Total lines of code has reduced, and coverage script is now out of date.\n' - 'Coverage is now $coveredPercent%, $coveredLines/$totalLines lines, but previously there were $targetLines lines.\n' - 'Update the "targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $totalLines).', - ); - exit(1); - } } coverageDirectory.deleteSync(recursive: true); diff --git a/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt b/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt index c7a8c7607d81..c25ffc272ada 100644 --- a/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt +++ b/packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,6 +96,7 @@ add_custom_command( ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ + VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" diff --git a/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc b/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc index dbda44723259..4d7a2dbcdc20 100644 --- a/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc +++ b/packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index b625a5cd67bc..dfd1a45f1af6 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -2,6 +2,7 @@ * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. +* Updates mockito to 5.2.0. ## 2.2.1 diff --git a/packages/shared_preferences/shared_preferences_android/android/build.gradle b/packages/shared_preferences/shared_preferences_android/android/build.gradle index ef0a6afef709..879464c4d9e4 100644 --- a/packages/shared_preferences/shared_preferences_android/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/android/build.gradle @@ -52,7 +52,7 @@ android { } dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:5.0.0' + testImplementation 'org.mockito:mockito-inline:5.2.0' } diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md index 5f5d8c246cd6..5cb8dc3b6120 100644 --- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0 + +* Updates web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. + ## 2.2.2 * Updates minimum supported SDK version to Dart 3.2. diff --git a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart index 9f0faae41367..0238ba578a45 100644 --- a/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart +++ b/packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart @@ -9,8 +9,7 @@ import 'package:shared_preferences_platform_interface/shared_preferences_platfor import 'package:shared_preferences_platform_interface/types.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; import 'package:shared_preferences_web/src/keys_extension.dart'; - -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml index bf6ca2a883df..44672c4e6425 100644 --- a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml @@ -11,11 +11,10 @@ dependencies: shared_preferences_platform_interface: ^2.3.0 shared_preferences_web: path: ../ - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter - js: ^0.6.3 diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml index ceb1dfebcf7a..1278a54a3e51 100644 --- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_web description: Web platform implementation of shared_preferences repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.2.2 +version: 2.3.0 environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,7 +22,7 @@ dependencies: flutter_web_plugins: sdk: flutter shared_preferences_platform_interface: ^2.3.0 - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt index c7a8c7607d81..c25ffc272ada 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,6 +96,7 @@ add_custom_command( ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ + VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" diff --git a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc index 944329afc03a..41d1b5cf736b 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc +++ b/packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 52ea3b087a7f..e1541589d45f 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.2.0 + +* Adds support for infinite rows and columns in TableView. + +## 0.1.2 + +* Fixes a layout issue for unpinned merged cells that follow pinned table spans. +* Updates outdated sample code. + +## 0.1.1 + +* Fixes a layout issue when pinned cells are merged. + ## 0.1.0 * [Breaking change] Adds support for merged cells in the TableView. diff --git a/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt b/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt index 930d2071a324..903f4899d6fc 100644 --- a/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt +++ b/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart index 5d1575b1f5af..9f2caa667d4a 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -41,6 +41,18 @@ import 'table_span.dart'; /// the [TableCellDelegateMixin]. The [TableView.builder] and [TableView.list] /// constructors create their own delegate. /// +/// A table with infinite rows and columns can be made by using a +/// [TableCellBuilderDelegate], or the [TableView.builder] constructor, and +/// omitting the row or column count. Returning null from the +/// [columnBuilder] or [rowBuilder] in this case will terminate +/// the row or column at that index, representing the end of the table in that +/// axis. In this scenario, until the potential end of the table in either +/// dimension is reached by returning null, the +/// [ScrollPosition.maxScrollExtent] will reflect [double.infinity]. This is +/// because as the table is built lazily, it will not know the end has been +/// reached until the [ScrollPosition] arrives there. This is similar to +/// returning null from [ListView.builder] to signify the end of the list. +/// /// This example shows a TableView of 100 children, all sized 100 by 100 /// pixels with a few [TableSpanDecoration]s like background colors and borders. /// The `builder` constructor is called on demand for the cells that are visible @@ -49,8 +61,10 @@ import 'table_span.dart'; /// ```dart /// TableView.builder( /// cellBuilder: (BuildContext context, TableVicinity vicinity) { -/// return Center( -/// child: Text('Cell ${vicinity.column} : ${vicinity.row}'), +/// return TableViewCell( +/// child: Center( +/// child: Text('Cell ${vicinity.column} : ${vicinity.row}'), +/// ), /// ); /// }, /// columnCount: 10, @@ -114,6 +128,16 @@ class TableView extends TwoDimensionalScrollView { /// This constructor generates a [TableCellBuilderDelegate] for building /// children on demand using the required [cellBuilder], /// [columnBuilder], and [rowBuilder]. + /// + /// For infinite rows and columns, omit providing [columnCount] or [rowCount]. + /// Returning null from the [columnBuilder] or [rowBuilder] will terminate + /// the row or column at that index, representing the end of the table in that + /// axis. In this scenario, until the potential end of the table in either + /// dimension is reached by returning null, the + /// [ScrollPosition.maxScrollExtent] will reflect [double.infinity]. This is + /// because as the table is built lazily, it will not know the end has been + /// reached until the [ScrollPosition] arrives there. This is similar to + /// returning null from [ListView.builder] to signify the end of the list. TableView.builder({ super.key, super.primary, @@ -127,17 +151,17 @@ class TableView extends TwoDimensionalScrollView { super.clipBehavior, int pinnedRowCount = 0, int pinnedColumnCount = 0, - required int columnCount, - required int rowCount, + int? columnCount, + int? rowCount, required TableSpanBuilder columnBuilder, required TableSpanBuilder rowBuilder, required TableViewCellBuilder cellBuilder, }) : assert(pinnedRowCount >= 0), - assert(rowCount >= 0), - assert(rowCount >= pinnedRowCount), - assert(columnCount >= 0), + assert(rowCount == null || rowCount >= 0), + assert(rowCount == null || rowCount >= pinnedRowCount), + assert(columnCount == null || columnCount >= 0), assert(pinnedColumnCount >= 0), - assert(columnCount >= pinnedColumnCount), + assert(columnCount == null || columnCount >= pinnedColumnCount), super( delegate: TableCellBuilderDelegate( columnCount: columnCount, @@ -300,13 +324,33 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final List _mergedColumns = []; // Cached Table metrics - Map _columnMetrics = {}; - Map _rowMetrics = {}; + final Map _columnMetrics = {}; + final Map _rowMetrics = {}; int? _firstNonPinnedRow; int? _firstNonPinnedColumn; int? _lastNonPinnedRow; int? _lastNonPinnedColumn; + int? _columnNullTerminatedIndex; + bool get _columnsAreInfinite => delegate.columnCount == null; + // How far columns should be laid out in a given frame. + double get _targetColumnPixel { + return cacheExtent + + horizontalOffset.pixels + + viewportDimension.width - + _pinnedColumnsExtent; + } + + int? _rowNullTerminatedIndex; + bool get _rowsAreInfinite => delegate.rowCount == null; + // How far rows should be laid out in a given frame. + double get _targetRowPixel { + return cacheExtent + + verticalOffset.pixels + + viewportDimension.height - + _pinnedRowsExtent; + } + TableVicinity? get _firstNonPinnedCell { if (_firstNonPinnedRow == null || _firstNonPinnedColumn == null) { return null; @@ -404,31 +448,79 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { return false; } - // Updates the cached metrics for the table. - // - // Will iterate through all columns and rows to define the layout pattern of - // the cells of the table. + // Updates the cached column metrics for the table. // - // TODO(Piinks): Add back infinite separately for easier review, https://github.com/flutter/flutter/issues/131226 - // Only relevant when the number of rows and columns is finite - void _updateAllMetrics() { - assert(needsDelegateRebuild || didResize); - - _firstNonPinnedColumn = null; - _lastNonPinnedColumn = null; - double startOfRegularColumn = 0; - double startOfPinnedColumn = 0; + // By default, existing column metrics will be updated if they have changed. + // Setting `appendColumns` to false will preserve existing metrics, adding + // additional metrics up to the visible+cacheExtent or up to a provided index, + // `toColumnIndex`. Appending is only relevant when the number of columns is + // infinite. + void _updateColumnMetrics({bool appendColumns = false, int? toColumnIndex}) { + assert(() { + if (toColumnIndex != null) { + // If we are computing up to an index, we must be appending. + return appendColumns; + } + return true; + }()); + double startOfRegularColumn = 0.0; + double startOfPinnedColumn = 0.0; + if (appendColumns) { + // We are only adding to the metrics we already know, since we are lazily + // compiling metrics. This should only be the case when the + // number of columns is infinite, and saves us going through all the + // columns we already know about. + assert(_columnsAreInfinite); + assert(_columnMetrics.isNotEmpty); + startOfPinnedColumn = + _columnMetrics[_firstNonPinnedColumn]?.trailingOffset ?? 0.0; + startOfRegularColumn = + _columnMetrics[_lastNonPinnedColumn]?.trailingOffset ?? 0.0; + } + // If we are computing up to a specific index, we are getting info for a + // merged cell, do not change the visible cells. + _firstNonPinnedColumn = + toColumnIndex == null ? null : _firstNonPinnedColumn; + _lastNonPinnedColumn = toColumnIndex == null ? null : _lastNonPinnedColumn; + int column = appendColumns ? _columnMetrics.length : 0; + + bool reachedColumnEnd() { + if (_columnsAreInfinite) { + if (toColumnIndex != null) { + // Column metrics should be computed up to the provided index. + // Only relevant when we are filling in missing column metrics in an + // infinite context. + return _columnMetrics.length > toColumnIndex; + } + // There are infinite columns, and no target index, compute metrics + // up to what is visible and in the cache extent, or the index that null + // terminates. + return _lastNonPinnedColumn != null || + _columnNullTerminatedIndex != null; + } + // Compute all the metrics if the columns are finite. + return column == delegate.columnCount!; + } - final Map newColumnMetrics = {}; - for (int column = 0; column < delegate.columnCount; column++) { + while (!reachedColumnEnd()) { final bool isPinned = column < delegate.pinnedColumnCount; final double leadingOffset = isPinned ? startOfPinnedColumn : startOfRegularColumn; _Span? span = _columnMetrics.remove(column); - assert(needsDelegateRebuild || span != null); - final TableSpan configuration = needsDelegateRebuild - ? delegate.buildColumn(column) - : span!.configuration; + final TableSpan? configuration = + span?.configuration ?? delegate.buildColumn(column); + if (configuration == null) { + // We have reached the end of columns based on a null termination. This + // This happens when a column count has not been specified. + assert(_columnsAreInfinite); + _lastNonPinnedColumn ??= column - 1; + _columnNullTerminatedIndex = column; + final bool acceptedDimension = _updateHorizontalScrollBounds(); + if (!acceptedDimension) { + _updateFirstAndLastVisibleCell(); + } + break; + } span ??= _Span(); span.update( isPinned: isPinned, @@ -441,17 +533,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { ), ), ); - newColumnMetrics[column] = span; + _columnMetrics[column] = span; if (!isPinned) { if (span.trailingOffset >= horizontalOffset.pixels && _firstNonPinnedColumn == null) { _firstNonPinnedColumn = column; } - final double targetColumnPixel = cacheExtent + - horizontalOffset.pixels + - viewportDimension.width - - startOfPinnedColumn; - if (span.trailingOffset >= targetColumnPixel && + if (span.trailingOffset >= _targetColumnPixel && _lastNonPinnedColumn == null) { _lastNonPinnedColumn = column; } @@ -459,27 +547,82 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { } else { startOfPinnedColumn = span.trailingOffset; } + column++; } - assert(newColumnMetrics.length >= delegate.pinnedColumnCount); - for (final _Span span in _columnMetrics.values) { - span.dispose(); - } - _columnMetrics = newColumnMetrics; - _firstNonPinnedRow = null; - _lastNonPinnedRow = null; - double startOfRegularRow = 0; - double startOfPinnedRow = 0; + assert(_columnMetrics.length >= delegate.pinnedColumnCount); + } + + // Updates the cached row metrics for the table. + // + // By default, existing row metrics will be updated if they have changed. + // Setting `appendRows` to false will preserve existing metrics, adding + // additional metrics up to the visible+cacheExtent or up to a provided index, + // `toRowIndex`. Appending is only relevant when the number of rows is + // infinite. + void _updateRowMetrics({bool appendRows = false, int? toRowIndex}) { + assert(() { + if (toRowIndex != null) { + // If we are computing up to an index, we must be appending. + return appendRows; + } + return true; + }()); + double startOfRegularRow = 0.0; + double startOfPinnedRow = 0.0; + if (appendRows) { + // We are only adding to the metrics we already know, since we are lazily + // compiling metrics. This should only be the case when the + // number of rows is infinite, and saves us going through all the + // rows we already know about. + assert(_rowsAreInfinite); + assert(_rowMetrics.isNotEmpty); + startOfPinnedRow = _rowMetrics[_firstNonPinnedRow]?.trailingOffset ?? 0.0; + startOfRegularRow = _rowMetrics[_lastNonPinnedRow]?.trailingOffset ?? 0.0; + } + // If we are computing up to a specific index, we are getting info for a + // merged cell, do not change the visible cells. + _firstNonPinnedRow = toRowIndex == null ? null : _firstNonPinnedRow; + _lastNonPinnedRow = toRowIndex == null ? null : _lastNonPinnedRow; + int row = appendRows ? _rowMetrics.length : 0; + + bool reachedRowEnd() { + if (_rowsAreInfinite) { + if (toRowIndex != null) { + // Row metrics should be computed up to the provided index. + // Only relevant when we are filling in missing column metrics in an + // infinite context. + return _rowMetrics.length > toRowIndex; + } + // There are infinite row, and no target index, compute metrics + // up to what is visible and in the cache extent, or the index that null + // terminates. + return _lastNonPinnedRow != null || _rowNullTerminatedIndex != null; + } + // Compute all the metrics if the rows are finite. + return row == delegate.rowCount!; + } - final Map newRowMetrics = {}; - for (int row = 0; row < delegate.rowCount; row++) { + while (!reachedRowEnd()) { final bool isPinned = row < delegate.pinnedRowCount; final double leadingOffset = isPinned ? startOfPinnedRow : startOfRegularRow; _Span? span = _rowMetrics.remove(row); - assert(needsDelegateRebuild || span != null); - final TableSpan configuration = - needsDelegateRebuild ? delegate.buildRow(row) : span!.configuration; + final TableSpan? configuration = + span?.configuration ?? delegate.buildRow(row); + if (configuration == null) { + // We have reached the end of rows based on a null termination. This + // This happens when a row count has not been specified, but we have + // reached the end. + assert(_rowsAreInfinite); + _lastNonPinnedRow ??= row - 1; + _rowNullTerminatedIndex = row; + final bool acceptedDimension = _updateVerticalScrollBounds(); + if (!acceptedDimension) { + _updateFirstAndLastVisibleCell(); + } + break; + } span ??= _Span(); span.update( isPinned: isPinned, @@ -492,17 +635,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { ), ), ); - newRowMetrics[row] = span; + _rowMetrics[row] = span; if (!isPinned) { if (span.trailingOffset >= verticalOffset.pixels && _firstNonPinnedRow == null) { _firstNonPinnedRow = row; } - final double targetRowPixel = cacheExtent + - verticalOffset.pixels + - viewportDimension.height - - startOfPinnedRow; - if (span.trailingOffset >= targetRowPixel && + if (span.trailingOffset > _targetRowPixel && _lastNonPinnedRow == null) { _lastNonPinnedRow = row; } @@ -510,32 +649,26 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { } else { startOfPinnedRow = span.trailingOffset; } + row++; } - assert(newRowMetrics.length >= delegate.pinnedRowCount); - for (final _Span span in _rowMetrics.values) { - span.dispose(); - } - _rowMetrics = newRowMetrics; - final double maxVerticalScrollExtent; - if (_rowMetrics.length <= delegate.pinnedRowCount) { - assert(_firstNonPinnedRow == null && _lastNonPinnedRow == null); - maxVerticalScrollExtent = 0.0; - } else { - final int lastRow = _rowMetrics.length - 1; - if (_firstNonPinnedRow != null) { - _lastNonPinnedRow ??= lastRow; - } - maxVerticalScrollExtent = math.max( - 0.0, - _rowMetrics[lastRow]!.trailingOffset - - viewportDimension.height + - startOfPinnedRow, - ); + assert(_rowMetrics.length >= delegate.pinnedRowCount); + } + + void _updateScrollBounds() { + final bool acceptedDimension = + _updateHorizontalScrollBounds() && _updateVerticalScrollBounds(); + if (!acceptedDimension) { + _updateFirstAndLastVisibleCell(); } + } + bool _updateHorizontalScrollBounds() { final double maxHorizontalScrollExtent; - if (_columnMetrics.length <= delegate.pinnedColumnCount) { + if (_columnsAreInfinite && _columnNullTerminatedIndex == null) { + maxHorizontalScrollExtent = double.infinity; + } else if (!_columnsAreInfinite && + _columnMetrics.length <= delegate.pinnedColumnCount) { assert(_firstNonPinnedColumn == null && _lastNonPinnedColumn == null); maxHorizontalScrollExtent = 0.0; } else { @@ -547,29 +680,61 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { 0.0, _columnMetrics[lastColumn]!.trailingOffset - viewportDimension.width + - startOfPinnedColumn, + _pinnedColumnsExtent, ); } + return horizontalOffset.applyContentDimensions( + 0.0, + maxHorizontalScrollExtent, + ); + } - final bool acceptedDimension = horizontalOffset.applyContentDimensions( - 0.0, maxHorizontalScrollExtent) && - verticalOffset.applyContentDimensions(0.0, maxVerticalScrollExtent); - if (!acceptedDimension) { - _updateFirstAndLastVisibleCell(); + bool _updateVerticalScrollBounds() { + final double maxVerticalScrollExtent; + if (_rowsAreInfinite && _rowNullTerminatedIndex == null) { + maxVerticalScrollExtent = double.infinity; + } else if (!_rowsAreInfinite && + _rowMetrics.length <= delegate.pinnedRowCount) { + assert(_firstNonPinnedRow == null && _lastNonPinnedRow == null); + maxVerticalScrollExtent = 0.0; + } else { + final int lastRow = _rowMetrics.length - 1; + if (_firstNonPinnedRow != null) { + _lastNonPinnedRow ??= lastRow; + } + maxVerticalScrollExtent = math.max( + 0.0, + _rowMetrics[lastRow]!.trailingOffset - + viewportDimension.height + + _pinnedRowsExtent, + ); } + return verticalOffset.applyContentDimensions( + 0.0, + maxVerticalScrollExtent, + ); } - // Uses the cached metrics to update the currently visible cells - // - // TODO(Piinks): Add back infinite separately for easier review, https://github.com/flutter/flutter/issues/131226 - // Only relevant when the number of rows and columns is finite + // Uses the cached metrics to update the currently visible cells. If the + // number of rows or columns are infinite, the layout is computed lazily, so + // this will call for an update to the metrics if we have scrolled beyond the + // layout portion we know about. void _updateFirstAndLastVisibleCell() { + if (_columnMetrics.isNotEmpty) { + _Span lastKnownColumn = _columnMetrics[_columnMetrics.length - 1]!; + if (_columnsAreInfinite && + lastKnownColumn.trailingOffset < _targetColumnPixel) { + // This will add the column metrics we do not know about up to the + // _targetColumnPixel, while keeping the ones we already know about. + _updateColumnMetrics(appendColumns: true); + lastKnownColumn = _columnMetrics[_columnMetrics.length - 1]!; + assert(_columnMetrics.length == delegate.columnCount || + lastKnownColumn.trailingOffset >= _targetColumnPixel || + _columnNullTerminatedIndex != null); + } + } _firstNonPinnedColumn = null; _lastNonPinnedColumn = null; - final double targetColumnPixel = cacheExtent + - horizontalOffset.pixels + - viewportDimension.width - - _pinnedColumnsExtent; for (int column = 0; column < _columnMetrics.length; column++) { if (_columnMetrics[column]!.isPinned) { continue; @@ -579,7 +744,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { _firstNonPinnedColumn == null) { _firstNonPinnedColumn = column; } - if (endOfColumn >= targetColumnPixel && _lastNonPinnedColumn == null) { + if (endOfColumn >= _targetColumnPixel && _lastNonPinnedColumn == null) { _lastNonPinnedColumn = column; break; } @@ -588,12 +753,20 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { _lastNonPinnedColumn ??= _columnMetrics.length - 1; } + if (_rowMetrics.isNotEmpty) { + _Span lastKnownRow = _rowMetrics[_rowMetrics.length - 1]!; + if (_rowsAreInfinite && lastKnownRow.trailingOffset < _targetRowPixel) { + // This will add the row metrics we do not know about up to the + // _targetRowPixel, while keeping the ones we already know about. + _updateRowMetrics(appendRows: true); + lastKnownRow = _rowMetrics[_rowMetrics.length - 1]!; + assert(_rowMetrics.length == delegate.rowCount || + lastKnownRow.trailingOffset >= _targetRowPixel || + _rowNullTerminatedIndex != null); + } + } _firstNonPinnedRow = null; _lastNonPinnedRow = null; - final double targetRowPixel = cacheExtent + - verticalOffset.pixels + - viewportDimension.height - - _pinnedRowsExtent; for (int row = 0; row < _rowMetrics.length; row++) { if (_rowMetrics[row]!.isPinned) { continue; @@ -602,7 +775,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { if (endOfRow >= verticalOffset.pixels && _firstNonPinnedRow == null) { _firstNonPinnedRow = row; } - if (endOfRow >= targetRowPixel && _lastNonPinnedRow == null) { + if (endOfRow >= _targetRowPixel && _lastNonPinnedRow == null) { _lastNonPinnedRow = row; break; } @@ -615,13 +788,27 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { @override void layoutChildSequence() { // Reset for a new frame + // We always reset the null terminating indices in case rows or columns have + // been added or removed. + _rowNullTerminatedIndex = null; + _columnNullTerminatedIndex = null; _mergedVicinities.clear(); _mergedRows.clear(); _mergedColumns.clear(); if (needsDelegateRebuild || didResize) { // Recomputes the table metrics, invalidates any cached information. - _updateAllMetrics(); + for (final _Span span in _columnMetrics.values) { + span.dispose(); + } + _columnMetrics.clear(); + for (final _Span span in _rowMetrics.values) { + span.dispose(); + } + _rowMetrics.clear(); + _updateColumnMetrics(); + _updateRowMetrics(); + _updateScrollBounds(); } else { // Updates the visible cells based on cached table metrics. _updateFirstAndLastVisibleCell(); @@ -694,7 +881,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { required int currentSpan, required int spanMergeStart, required int spanMergeEnd, - required int spanCount, + required int? spanCount, required int pinnedSpanCount, required TableVicinity currentVicinity, }) { @@ -710,7 +897,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { 'than the current $lowerSpanOrientation at $currentVicinity.', ); assert( - spanMergeEnd < spanCount, + spanCount == null || spanMergeEnd < spanCount, '$spanOrientation merge configuration exceeds number of ' '${lowerSpanOrientation}s in the table. $spanOrientation merge ' 'containing $currentVicinity starts at $spanMergeStart, and ends at ' @@ -738,6 +925,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { double rowOffset = -offset.dy; for (int row = start.row; row <= end.row; row += 1) { double columnOffset = -offset.dx; + assert(row < _rowMetrics.length); rowSpan = _rowMetrics[row]!; final double standardRowHeight = rowSpan.extent; double? mergedRowHeight; @@ -745,6 +933,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { rowOffset += rowSpan.configuration.padding.leading; for (int column = start.column; column <= end.column; column += 1) { + assert(column < _columnMetrics.length); colSpan = _columnMetrics[column]!; final double standardColumnWidth = colSpan.extent; double? mergedColumnWidth; @@ -801,17 +990,73 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { // | <--------- extent of merged cell ---------> | // Compute height and layout offset for merged rows. - mergedRowOffset = -verticalOffset.pixels + + final bool rowIsInPinnedColumn = _lastPinnedColumn != null && + vicinity.column <= _lastPinnedColumn!; + final bool rowIsPinned = + _lastPinnedRow != null && firstRow <= _lastPinnedRow!; + final double baseRowOffset = + switch ((rowIsInPinnedColumn, rowIsPinned)) { + // Both row and column are pinned at this cell, or just pinned row. + (true, true) || (false, true) => 0.0, + // Cell is within a pinned column, or no pinned area at all. + (true, false) || + (false, false) => + _pinnedRowsExtent - verticalOffset.pixels, + }; + mergedRowOffset = baseRowOffset + _rowMetrics[firstRow]!.leadingOffset + _rowMetrics[firstRow]!.configuration.padding.leading; + if (_rowsAreInfinite && _rowMetrics[lastRow] == null) { + // The number of rows is infinte, and we have not calculated + // the metrics to the full extent of the merged cell. Update the + // metrics so we have all the information for the merged area. + _updateRowMetrics(appendRows: true, toRowIndex: lastRow); + } + assert( + _rowMetrics[lastRow] != null, + 'The merged cell containing $vicinity is missing TableSpan ' + 'information necessary for layout. The rowBuilder returned ' + 'null, signifying the end, at row $_rowNullTerminatedIndex but the ' + 'merged cell is configured to end with row $lastRow.', + ); mergedRowHeight = _rowMetrics[lastRow]!.trailingOffset - _rowMetrics[firstRow]!.leadingOffset - _rowMetrics[lastRow]!.configuration.padding.trailing - _rowMetrics[firstRow]!.configuration.padding.leading; // Compute width and layout offset for merged columns. - mergedColumnOffset = -horizontalOffset.pixels + + final bool columnIsInPinnedRow = + _lastPinnedRow != null && vicinity.row <= _lastPinnedRow!; + final bool columnIsPinned = + _lastPinnedColumn != null && firstColumn <= _lastPinnedColumn!; + final double baseColumnOffset = + switch ((columnIsInPinnedRow, columnIsPinned)) { + // Both row and column are pinned at this cell, or just pinned column. + (true, true) || (false, true) => 0.0, + // Cell is within a pinned row, or no pinned area at all. + (true, false) || + (false, false) => + _pinnedColumnsExtent - horizontalOffset.pixels, + }; + mergedColumnOffset = baseColumnOffset + _columnMetrics[firstColumn]!.leadingOffset + _columnMetrics[firstColumn]!.configuration.padding.leading; + + if (_columnsAreInfinite && _columnMetrics[lastColumn] == null) { + // The number of columns is infinte, and we have not calculated + // the metrics to the full extent of the merged cell. Update the + // metrics so we have all the information for the merged area. + _updateColumnMetrics( + appendColumns: true, + toColumnIndex: lastColumn, + ); + } + assert( + _columnMetrics[lastColumn] != null, + 'The merged cell containing $vicinity is missing TableSpan ' + 'information necessary for layout. The columnBuilder returned ' + 'null, signifying the end, at column $_columnNullTerminatedIndex but ' + 'the merged cell is configured to end with column $lastColumn.', + ); mergedColumnWidth = _columnMetrics[lastColumn]!.trailingOffset - _columnMetrics[firstColumn]!.leadingOffset - _columnMetrics[lastColumn]!.configuration.padding.trailing - @@ -1012,7 +1257,10 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { // A merged cell spans multiple vicinities, but only lays out one child for // the full area. Returns the child that has been laid out to span the given // vicinity. - assert(_mergedVicinities.keys.contains(vicinity)); + assert( + _mergedVicinities.keys.contains(vicinity), + 'The vicinity $vicinity is not accounted for as covered by a merged cell.', + ); final TableVicinity mergedVicinity = _mergedVicinities[vicinity]!; // This vicinity must resolve to a child, unless something has gone wrong! return getChildFor( @@ -1440,6 +1688,12 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { _clipPinnedRowsHandle.layer = null; _clipPinnedColumnsHandle.layer = null; _clipCellsHandle.layer = null; + for (final _Span span in _rowMetrics.values) { + span.dispose(); + } + for (final _Span span in _columnMetrics.values) { + span.dispose(); + } super.dispose(); } } diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart index 39a8d676a549..11ea2459ea08 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart @@ -14,7 +14,10 @@ import 'table_span.dart'; /// Used by the [TableCellDelegateMixin.buildColumn] and /// [TableCellDelegateMixin.buildRow] to configure rows and columns in the /// [TableView]. -typedef TableSpanBuilder = TableSpan Function(int index); +/// +/// Returning null from this builder signifies the end of rows or columns being +/// built if a row or column count has not been specified for the table. +typedef TableSpanBuilder = TableSpan? Function(int index); /// Signature for a function that creates a child [TableViewCell] for a given /// [TableVicinity] in a [TableView], but may return null. @@ -33,9 +36,8 @@ mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { /// /// The [buildColumn] method will be called for indices smaller than the value /// provided here to learn more about the extent and visual appearance of a - /// particular column. - // TODO(Piinks): land infinite separately, https://github.com/flutter/flutter/issues/131226 - // If null, the table will have an infinite number of columns. + /// particular column. If null, the table will have an infinite number of + /// columns, unless [buildColumn] returns null to signify the end. /// /// The value returned by this getter may be an estimate of the total /// available columns, but [buildColumn] method must provide a valid @@ -46,15 +48,18 @@ mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { /// /// If the value returned by this getter changes throughout the lifetime of /// the delegate object, [notifyListeners] must be called. - int get columnCount; + /// + /// When null, the number of columns will be infinite in number, unless null + /// is returned from [TableCellBuilderDelegate.columnBuilder]. The + /// [TableCellListDelegate] does not support an infinite number of columns. + int? get columnCount; /// The number of rows that the table has content for. /// /// The [buildRow] method will be called for indices smaller than the value /// provided here to learn more about the extent and visual appearance of a - /// particular row. - // TODO(Piinks): land infinite separately, https://github.com/flutter/flutter/issues/131226 - // If null, the table will have an infinite number of rows. + /// particular row. If null, the table will have an infinite number of rows, + /// unless [buildRow] returns null to signify the end. /// /// The value returned by this getter may be an estimate of the total /// available rows, but [buildRow] method must provide a valid @@ -65,7 +70,11 @@ mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { /// /// If the value returned by this getter changes throughout the lifetime of /// the delegate object, [notifyListeners] must be called. - int get rowCount; + /// + /// When null, the number of rows will be infinite in number, unless null + /// is returned from [TableCellBuilderDelegate.rowBuilder]. The + /// [TableCellListDelegate] does not support an infinite number of rows. + int? get rowCount; /// The number of columns that are permanently shown on the leading vertical /// edge of the viewport. @@ -104,14 +113,18 @@ mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { /// Builds the [TableSpan] that describes the column at the provided index. /// /// The builder must return a valid [TableSpan] for all indices smaller than - /// [columnCount]. - TableSpan buildColumn(int index); + /// [columnCount]. If [columnCount] is null, the number of columns will be + /// infinite, unless this builder returns null to signal the end of the + /// columns. + TableSpan? buildColumn(int index); /// Builds the [TableSpan] that describe the row at the provided index. /// /// The builder must return a valid [TableSpan] for all indices smaller than - /// [rowCount]. - TableSpan buildRow(int index); + /// [rowCount]. If [rowCount] is null, the number of rows will be + /// infinite, unless this builder returns null to signal the end of the + /// columns. + TableSpan? buildRow(int index); } /// A delegate that supplies children for a [TableViewport] on demand using a @@ -120,12 +133,17 @@ mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { /// Unlike the base [TwoDimensionalChildBuilderDelegate] this delegate does not /// automatically insert repaint boundaries. Instead, repaint boundaries are /// controlled by [TableViewCell.addRepaintBoundaries]. +/// +/// If the [rowCount] or [columnCount] is not provided, the number of rows +/// and/or columns will be infinite. Returning null from the [columnBuilder] +/// and/or [rowBuilder] in this case can terminate the number of rows and +/// columns at the given index. class TableCellBuilderDelegate extends TwoDimensionalChildBuilderDelegate with TableCellDelegateMixin { /// Creates a lazy building delegate to use with a [TableView]. TableCellBuilderDelegate({ - required int columnCount, - required int rowCount, + int? columnCount, + int? rowCount, int pinnedColumnCount = 0, int pinnedRowCount = 0, super.addAutomaticKeepAlives, @@ -134,10 +152,10 @@ class TableCellBuilderDelegate extends TwoDimensionalChildBuilderDelegate required TableSpanBuilder rowBuilder, }) : assert(pinnedColumnCount >= 0), assert(pinnedRowCount >= 0), - assert(rowCount >= 0), - assert(columnCount >= 0), - assert(pinnedColumnCount <= columnCount), - assert(pinnedRowCount <= rowCount), + assert(rowCount == null || rowCount >= 0), + assert(columnCount == null || columnCount >= 0), + assert(columnCount == null || pinnedColumnCount <= columnCount), + assert(rowCount == null || pinnedRowCount <= rowCount), _rowBuilder = rowBuilder, _columnBuilder = columnBuilder, _pinnedColumnCount = pinnedColumnCount, @@ -145,33 +163,36 @@ class TableCellBuilderDelegate extends TwoDimensionalChildBuilderDelegate super( builder: (BuildContext context, ChildVicinity vicinity) => cellBuilder(context, vicinity as TableVicinity), - maxXIndex: columnCount - 1, - maxYIndex: rowCount - 1, + maxXIndex: columnCount == null ? columnCount : columnCount - 1, + maxYIndex: rowCount == null ? rowCount : rowCount - 1, // repaintBoundaries handled by TableViewCell addRepaintBoundaries: false, ); @override - int get columnCount => maxXIndex! + 1; - set columnCount(int value) { - assert(pinnedColumnCount <= value); - maxXIndex = value - 1; + int? get columnCount => maxXIndex == null ? null : maxXIndex! + 1; + + set columnCount(int? value) { + assert(value == null || pinnedColumnCount <= value); + maxXIndex = value == null ? null : value - 1; } /// Builds the [TableSpan] that describes the column at the provided index. /// /// The builder must return a valid [TableSpan] for all indices smaller than - /// [columnCount]. + /// [columnCount]. If [columnCount] is null, the number of columns will be + /// infinite, unless this builder returns null to signal the end of the + /// columns. final TableSpanBuilder _columnBuilder; @override - TableSpan buildColumn(int index) => _columnBuilder(index); + TableSpan? buildColumn(int index) => _columnBuilder(index); @override int get pinnedColumnCount => _pinnedColumnCount; int _pinnedColumnCount; set pinnedColumnCount(int value) { assert(value >= 0); - assert(value <= columnCount); + assert(columnCount == null || value <= columnCount!); if (pinnedColumnCount == value) { return; } @@ -180,26 +201,29 @@ class TableCellBuilderDelegate extends TwoDimensionalChildBuilderDelegate } @override - int get rowCount => maxYIndex! + 1; - set rowCount(int value) { - assert(pinnedRowCount <= value); - maxYIndex = value - 1; + int? get rowCount => maxYIndex == null ? null : maxYIndex! + 1; + + set rowCount(int? value) { + assert(value == null || pinnedRowCount <= value); + maxYIndex = value == null ? null : value - 1; } /// Builds the [TableSpan] that describes the row at the provided index. /// /// The builder must return a valid [TableSpan] for all indices smaller than - /// [rowCount]. + /// [rowCount]. If [rowCount] is null, the number of rows will be + /// infinite, unless this builder returns null to signal the end of the + /// rows. final TableSpanBuilder _rowBuilder; @override - TableSpan buildRow(int index) => _rowBuilder(index); + TableSpan? buildRow(int index) => _rowBuilder(index); @override int get pinnedRowCount => _pinnedRowCount; int _pinnedRowCount; set pinnedRowCount(int value) { assert(value >= 0); - assert(value <= rowCount); + assert(rowCount == null || value <= rowCount!); if (pinnedRowCount == value) { return; } @@ -261,7 +285,13 @@ class TableCellListDelegate extends TwoDimensionalChildListDelegate /// [columnCount]. final TableSpanBuilder _columnBuilder; @override - TableSpan buildColumn(int index) => _columnBuilder(index); + TableSpan? buildColumn(int index) { + if (index >= columnCount) { + // The list delegate has a finite number of columns. + return null; + } + return _columnBuilder(index); + } @override int get pinnedColumnCount => _pinnedColumnCount; @@ -285,7 +315,13 @@ class TableCellListDelegate extends TwoDimensionalChildListDelegate /// [rowCount]. final TableSpanBuilder _rowBuilder; @override - TableSpan buildRow(int index) => _rowBuilder(index); + TableSpan? buildRow(int index) { + if (index >= rowCount) { + // The list deleagte has a finite number of rows. + return null; + } + return _rowBuilder(index); + } @override int get pinnedRowCount => _pinnedRowCount; diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index e67277843dee..fb9dc192f702 100644 --- a/packages/two_dimensional_scrollables/pubspec.yaml +++ b/packages/two_dimensional_scrollables/pubspec.yaml @@ -1,6 +1,6 @@ name: two_dimensional_scrollables description: Widgets that scroll using the two dimensional scrolling foundation. -version: 0.1.0 +version: 0.2.0 repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+ diff --git a/packages/two_dimensional_scrollables/test/table_view/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart index 7d59ad8aa573..ce879b75b277 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -2,6 +2,7 @@ // 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 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -181,6 +182,1861 @@ void main() { ); expect(tableView, isNull); }); + + group('Infinite spans - ', () { + late ScrollController verticalController; + late ScrollController horizontalController; + const TableSpan largeSpan = TableSpan(extent: FixedTableSpanExtent(200)); + + setUp(() { + verticalController = ScrollController(); + horizontalController = ScrollController(); + }); + + tearDown(() { + verticalController.dispose(); + horizontalController.dispose(); + }); + + TableView getTableView({ + int? columnCount, + int? rowCount, + TableSpanBuilder? columnBuilder, + TableSpanBuilder? rowBuilder, + TableViewCellBuilder? cellBuilder, + int pinnedColumnCount = 0, + int pinnedRowCount = 0, + }) { + return TableView.builder( + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + columnCount: columnCount, + pinnedColumnCount: pinnedColumnCount, + columnBuilder: columnBuilder ?? (_) => largeSpan, + rowCount: rowCount, + pinnedRowCount: pinnedRowCount, + rowBuilder: rowBuilder ?? (_) => largeSpan, + cellBuilder: cellBuilder ?? + (_, TableVicinity vicinity) { + return TableViewCell( + child: Text('R${vicinity.row}:C${vicinity.column}'), + ); + }, + ); + } + + testWidgets('infinite rows, columns are finite', + (WidgetTester tester) async { + // Nothing pinned --- + await tester.pumpWidget(MaterialApp( + home: getTableView(columnCount: 10), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out before row 5, or after row 9. + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R10:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedColumnCount: 1, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out before row 5, or after row 9. + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R10:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedRowCount: 1, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out before row 5, or after row 9, except for pinned row + // 0. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R1:C0'), findsNothing); + expect(find.text('R10:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedColumnCount: 1, + pinnedRowCount: 1, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out before row 5, or after row 9, except for pinned row + // 0. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R1:C0'), findsNothing); + expect(find.text('R10:C0'), findsNothing); + }); + + testWidgets('infinite columns, rows are finite', + (WidgetTester tester) async { + // Nothing pinned --- + await tester.pumpWidget(MaterialApp( + home: getTableView(rowCount: 10), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. + horizontalController.jumpTo(1200.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12. + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C12'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView(rowCount: 10, pinnedColumnCount: 1), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. + horizontalController.jumpTo(1200.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12, except for + // pinned column. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C12'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView(rowCount: 10, pinnedRowCount: 1), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. + horizontalController.jumpTo(1200.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12. + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C12'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + pinnedRowCount: 1, + pinnedColumnCount: 1, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. + horizontalController.jumpTo(1200.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12, except for + // pinned column. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C12'), findsNothing); + }); + + testWidgets('infinite rows & columns', (WidgetTester tester) async { + // No pinned --- + await tester.pumpWidget(MaterialApp( + home: getTableView(), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5, no rows beyond row 4. + expect(find.text('R0:C6'), findsNothing); + expect(find.text('R5:C0'), findsNothing); + // Change both scroll offsets, validate more columns and rows were + // populated. + horizontalController.jumpTo(1200.0); + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12. + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C12'), findsNothing); + // No rows laid out before row 4, or after row 9. + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R10:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView(pinnedColumnCount: 1), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5, no rows beyond row 4. + expect(find.text('R0:C6'), findsNothing); + expect(find.text('R5:C0'), findsNothing); + // Change both scroll offsets, validate more columns and rows were + // populated. + horizontalController.jumpTo(1200.0); + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12, except for + // pinned first column. + expect(find.text('R5:C0'), findsOneWidget); + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C12'), findsNothing); + // No rows laid out before row 4, or after row 9. + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R10:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned Rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView(pinnedRowCount: 1), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5, no rows beyond row 4. + expect(find.text('R0:C6'), findsNothing); + expect(find.text('R5:C0'), findsNothing); + // Change both scroll offsets, validate more columns and rows were + // populated. + horizontalController.jumpTo(1200.0); + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12. + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C12'), findsNothing); + // No rows laid out before row 4, or after row 9, except for pinned + // first row. + expect(find.text('R0:C6'), findsOneWidget); + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R10:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + pinnedRowCount: 1, + pinnedColumnCount: 1, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5, no rows beyond row 4. + expect(find.text('R0:C6'), findsNothing); + expect(find.text('R5:C0'), findsNothing); + // Change both scroll offsets, validate more columns and rows were + // populated. + horizontalController.jumpTo(1200.0); + verticalController.jumpTo(1000.0); + await tester.pump(); + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R9:C11'), findsOneWidget); + expect( + tester.getRect(find.text('R9:C11')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out before column 5, or after column 12, except for + // pinned first column. + expect(find.text('R5:C0'), findsOneWidget); + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C12'), findsNothing); + // No rows laid out before row 4, or after row 9, except for pinned + // first row. + expect(find.text('R0:C6'), findsOneWidget); + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R10:C6'), findsNothing); + }); + + testWidgets('infinite rows can null terminate', + (WidgetTester tester) async { + // Nothing pinned --- + bool calledOutOfBounds = false; + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + if (index > 8) { + calledOutOfBounds = true; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + // This exceeds the bounds of the scroll view once the rows have been + // null terminated. + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // After null terminating, the builder was not called further. + expect(calledOutOfBounds, isFalse); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, or after row 7. + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R8:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedColumnCount: 1, + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + // This exceeds the bounds of the scroll view once the rows have been + // null terminated. + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, or after row 7. + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R8:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedRowCount: 1, + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + // This exceeds the bounds of the scroll view once the rows have been + // null terminated. + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, or after row 7, except for the first + // pinned row. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R1:C0'), findsNothing); + expect(find.text('R8:C0'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + pinnedColumnCount: 1, + pinnedRowCount: 1, + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 800.0, 1000.0, 1000.0), + ); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the vertical scroll offset, validate more rows were populated. + // This exceeds the bounds of the scroll view once the rows have been + // null terminated. + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, or after row 7, except for the first + // pinned row. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R1:C0'), findsNothing); + expect(find.text('R8:C0'), findsNothing); + }); + + testWidgets('Null terminated rows will update', + (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + // Change the vertical scroll offset, validate more rows were populated. + // This exceeds the bounds of the scroll view once the rows have been + // null terminated. + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, or after row 7. + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R8:C0'), findsNothing); + + // Increase the number of rows + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + rowBuilder: (int index) { + // There will only be 16 rows. + if (index == 16) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + + // The position should not have changed. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect we no longer know where the + // end is, until the rowBuilder returns null again. + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, 1200.0); + // The layout should not have changed. + expect(find.text('R5:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out before row 5, but more rows were laid out into the + // newly updated cacheExtent (row 8). + expect(find.text('R0:C0'), findsNothing); + expect(find.text('R8:C0'), findsOneWidget); + // This exceeds the new bounds. + verticalController.jumpTo(3200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 2600.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to reflect reaching the end of the rows + // after returning null again at the new index. + expect(verticalController.position.maxScrollExtent, 2600.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + + // Decrease the number of rows + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnCount: 10, + rowBuilder: (int index) { + // There will only be 5 rows. + if (index == 5) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + + // The position should have changed. + expect(verticalController.position.pixels, 400.0); + expect(horizontalController.position.pixels, 0.0); + // Max scroll extent was updated to the new end we have corrected to. + expect(verticalController.position.maxScrollExtent, 400.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + // The layout updated. + expect(find.text('R2:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(800.0, 400.0, 1000.0, 600.0), + ); + // No rows laid out after row 5. + expect(find.text('R5:C0'), findsNothing); + }); + + testWidgets('Null terminated columns will update', + (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + columnBuilder: (int index) { + // There will only be 8 columns. + if (index == 8) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + // Change the horizontal scroll offset, validate more columns were + // populated. This exceeds the bounds of the scroll view once the + // columns have been null terminated. + horizontalController.jumpTo(1400.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 800.0); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 800.0); + expect(find.text('R0:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C5')), + const Rect.fromLTRB(200.0, 0.0, 400.0, 200.0), + ); + expect(find.text('R4:C7'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C7')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 3, or after column 7. + expect(find.text('R0:C2'), findsNothing); + expect(find.text('R0:C8'), findsNothing); + + // Increase the number of rows + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + columnBuilder: (int index) { + // There will only be 16 column. + if (index == 16) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + + // The position should not have changed. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 800.0); + // Max scroll extent was updated to reflect we no longer know where the + // end is, until the rowBuilder returns null again. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, double.infinity); + // The layout should not have changed. + expect(find.text('R0:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C5')), + const Rect.fromLTRB(200.0, 0.0, 400.0, 200.0), + ); + expect(find.text('R4:C7'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C7')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 3, but after column 7 we have added + // new columns. + expect(find.text('R0:C2'), findsNothing); + expect(find.text('R0:C8'), findsOneWidget); + // This exceeds the new bounds. + horizontalController.jumpTo(3200.0); + await tester.pumpAndSettle(); + // Position was corrected. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 2400.0); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null again at the new index. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 2400.0); + + // Decrease the number of columns + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + columnBuilder: (int index) { + // There will only be 5 columns. + if (index == 5) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + + // The position should have changed. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 200.0); + // Max scroll extent was updated to the new end we have corrected to. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 200.0); + // The layout updated. + expect(find.text('R0:C2'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTRB(200.0, 0.0, 400.0, 200.0), + ); + expect(find.text('R4:C4'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C4')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out after column 5. + expect(find.text('R0:C5'), findsNothing); + }); + + testWidgets('infinite columns can null terminate', + (WidgetTester tester) async { + // Nothing pinned --- + bool calledOutOfBounds = false; + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + if (index > 10) { + calledOutOfBounds = true; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.00); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. This exceeds the bounds of the scroll view once the + // columns have been null terminated. + horizontalController.jumpTo(1400.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + // Position was corrected. + expect(horizontalController.position.pixels, 1200.0); + // After null terminating, the builder was not called further. + expect(calledOutOfBounds, isFalse); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C9')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 5, or after column 9. + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C10'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + pinnedColumnCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.00); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. This exceeds the bounds of the scroll view once the + // columns have been null terminated. + horizontalController.jumpTo(1400.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + // Position was corrected. + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C9')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 5, or after column 9, except for + // the pinned first column. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C10'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + pinnedRowCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.00); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. This exceeds the bounds of the scroll view once the + // columns have been null terminated. + horizontalController.jumpTo(1400.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + // Position was corrected. + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C9')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 5, or after column 9. + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C10'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowCount: 10, + pinnedRowCount: 1, + pinnedColumnCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 1400.00); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // Change the horizontal scroll offset, validate more columns were + // populated. This exceeds the bounds of the scroll view once the + // columns have been null terminated. + horizontalController.jumpTo(1400.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + // Position was corrected. + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extent was updated to reflect reaching the end of the + // columns after returning null. + expect(verticalController.position.maxScrollExtent, 1400.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R0:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C9')), + const Rect.fromLTRB(600.0, 800.0, 800.0, 1000.0), + ); + // No columns laid out before column 5, or after column 9, except for + // the pinned first column. + expect(find.text('R0:C0'), findsOneWidget); + expect(find.text('R0:C4'), findsNothing); + expect(find.text('R0:C10'), findsNothing); + }); + testWidgets('infinite rows & columns can null terminate', + (WidgetTester tester) async { + // Nothing pinned --- + bool calledRowOutOfBounds = false; + bool calledColumnOutOfBounds = false; + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + if (index > 8) { + calledRowOutOfBounds = true; + } + return largeSpan; + }, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + if (index > 10) { + calledColumnOutOfBounds = true; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the scroll offsets, validate more columns and rows were + // populated. + // These exceed the bounds of the scroll view once the column and row + // builders have null terminated. + horizontalController.jumpTo(1400.0); + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected for both. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + // After null terminating, the builders were not called further. + expect(calledRowOutOfBounds, isFalse); + expect(calledColumnOutOfBounds, isFalse); + // Max scroll extents were updated to reflect reaching the ends after + // returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C9')), + const Rect.fromLTRB(600.0, 400.0, 800.0, 600.0), + ); + // No columns laid out before column 5, or after column 9. + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C10'), findsNothing); + // No rows laid out before row 4, or after row 7. + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R8:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + pinnedColumnCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the scroll offsets, validate more columns and rows were + // populated. + // These exceed the bounds of the scroll view once the column and row + // builders have null terminated. + horizontalController.jumpTo(1400.0); + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected for both. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extents were updated to reflect reaching the ends after + // returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C9')), + const Rect.fromLTRB(600.0, 400.0, 800.0, 600.0), + ); + // No columns laid out before column 5, or after column 9, except for + // the pinned first column. + expect(find.text('R5:C0'), findsOneWidget); + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C10'), findsNothing); + // No rows laid out before row 4, or after row 7. + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R8:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + pinnedRowCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the scroll offsets, validate more columns and rows were + // populated. + // These exceed the bounds of the scroll view once the column and row + // builders have null terminated. + horizontalController.jumpTo(1400.0); + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected for both. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extents were updated to reflect reaching the ends after + // returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C9')), + const Rect.fromLTRB(600.0, 400.0, 800.0, 600.0), + ); + // No columns laid out before column 5, or after column 9. + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C10'), findsNothing); + // No rows laid out before row 4, or after row 7, except for first + // pinned row. + expect(find.text('R0:C6'), findsOneWidget); + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R8:C6'), findsNothing); + + await tester.pumpWidget(Container()); + + // Pinned columns and rows --- + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowBuilder: (int index) { + // There will only be 8 rows. + if (index == 8) { + return null; + } + return largeSpan; + }, + pinnedRowCount: 1, + pinnedColumnCount: 1, + columnBuilder: (int index) { + // There will only be 10 columns. + if (index == 10) { + return null; + } + return largeSpan; + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R4:C5'), findsOneWidget); + expect( + tester.getRect(find.text('R4:C5')), + const Rect.fromLTRB(1000.0, 800.0, 1200.0, 1000.0), + ); + // No columns laid out beyond column 5. + expect(find.text('R0:C6'), findsNothing); + // No rows laid out beyond row 4. + expect(find.text('R5:C0'), findsNothing); + // Change the scroll offsets, validate more columns and rows were + // populated. + // These exceed the bounds of the scroll view once the column and row + // builders have null terminated. + horizontalController.jumpTo(1400.0); + verticalController.jumpTo(1200.0); + await tester.pumpAndSettle(); + // Position was corrected for both. + expect(verticalController.position.pixels, 1000.0); + expect(horizontalController.position.pixels, 1200.0); + // Max scroll extents were updated to reflect reaching the ends after + // returning null. + expect(verticalController.position.maxScrollExtent, 1000.0); + expect(horizontalController.position.maxScrollExtent, 1200.0); + expect(find.text('R5:C6'), findsOneWidget); + expect( + tester.getRect(find.text('R5:C6')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0), + ); + expect(find.text('R7:C9'), findsOneWidget); + expect( + tester.getRect(find.text('R7:C9')), + const Rect.fromLTRB(600.0, 400.0, 800.0, 600.0), + ); + // No columns laid out before column 5, or after column 9, except for + //the first pinned column. + expect(find.text('R5:C0'), findsOneWidget); + expect(find.text('R5:C4'), findsNothing); + expect(find.text('R5:C10'), findsNothing); + // No rows laid out before row 4, or after row 7, except for first + // pinned row. + expect(find.text('R0:C6'), findsOneWidget); + expect(find.text('R3:C6'), findsNothing); + expect(find.text('R8:C6'), findsNothing); + }); + testWidgets('merged cells work with lazy layout computation', + (WidgetTester tester) async { + // When columns and rows are finite, the layout is eagerly computed and + // the children are lazily laid out. This makes computing merged cell + // layouts easy. In an infinite world, the layout is also lazily + // computed, so not all of the information may be available for a merged + // cell if it extends into an area we have not computed the layout for + // yet. + const ({int start, int span}) rowConfig = (start: 0, span: 10); + final List mergedRows = List.generate( + 10, + (int index) => index, + ); + const ({int start, int span}) columnConfig = (start: 1, span: 10); + final List mergedColumns = List.generate( + 10, + (int index) => index + 1, + ); + await tester.pumpWidget(MaterialApp( + home: getTableView( + cellBuilder: (_, TableVicinity vicinity) { + // Merged row + if (mergedRows.contains(vicinity.row) && vicinity.column == 0) { + return TableViewCell( + rowMergeStart: rowConfig.start, + rowMergeSpan: rowConfig.span, + child: const Text('R0:C0'), + ); + } + // Merged column + if (mergedColumns.contains(vicinity.column) && + vicinity.row == 0) { + return TableViewCell( + columnMergeStart: columnConfig.start, + columnMergeSpan: columnConfig.span, + child: const Text('R0:C1'), + ); + } + return TableViewCell( + child: Text('R${vicinity.row}:C${vicinity.column}'), + ); + }, + ), + )); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, double.infinity); + expect(horizontalController.position.maxScrollExtent, double.infinity); + expect(find.text('R0:C0'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTRB(0.0, 0.0, 200.0, 2000.0), + ); + expect(find.text('R0:C1'), findsOneWidget); + expect( + tester.getRect(find.text('R0:C1')), + const Rect.fromLTRB(200.0, 0.0, 2200.0, 200.0), + ); + expect(find.text('R1:C1'), findsOneWidget); + expect( + tester.getRect(find.text('R1:C1')), + const Rect.fromLTRB(200.0, 200.0, 400.0, 400.0), + ); + }); + + testWidgets('merged column that exceeds metrics will assert', + (WidgetTester tester) async { + final List exceptions = []; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + const ({int start, int span}) columnConfig = (start: 1, span: 10); + final List mergedColumns = List.generate( + 10, + (int index) => index + 1, + ); + await tester.pumpWidget(MaterialApp( + home: getTableView( + columnBuilder: (int index) { + // There will only be 8 columns, but the merge is set up for 10. + if (index == 8) { + return null; + } + return largeSpan; + }, + cellBuilder: (_, TableVicinity vicinity) { + // Merged column + if (mergedColumns.contains(vicinity.column) && + vicinity.row == 0) { + return TableViewCell( + columnMergeStart: columnConfig.start, + columnMergeSpan: columnConfig.span, + child: const Text('R0:C1'), + ); + } + return TableViewCell( + child: Text('R${vicinity.row}:C${vicinity.column}'), + ); + }, + ), + )); + await tester.pumpWidget(Container()); + FlutterError.onError = oldHandler; + expect(exceptions.length, 3); + expect( + exceptions.first.toString(), + contains( + 'The merged cell containing (row: 0, column: 1) is ' + 'missing TableSpan information necessary for layout. The ' + 'columnBuilder returned null, signifying the end, at column 8 but ' + 'the merged cell is configured to end with column 10.', + ), + ); + }); + + testWidgets('merged row that exceeds metrics will assert', + (WidgetTester tester) async { + final List exceptions = []; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + const ({int start, int span}) rowConfig = (start: 0, span: 10); + final List mergedRows = List.generate( + 10, + (int index) => index, + ); + await tester.pumpWidget(MaterialApp( + home: getTableView( + rowBuilder: (int index) { + // There will only be 8 rows, but the merge is set up for 9. + if (index == 8) { + return null; + } + return largeSpan; + }, + cellBuilder: (_, TableVicinity vicinity) { + // Merged column + if (mergedRows.contains(vicinity.row) && vicinity.column == 0) { + return TableViewCell( + rowMergeStart: rowConfig.start, + rowMergeSpan: rowConfig.span, + child: const Text('R0:C0'), + ); + } + return TableViewCell( + child: Text('R${vicinity.row}:C${vicinity.column}'), + ); + }, + ), + )); + await tester.pumpWidget(Container()); + FlutterError.onError = oldHandler; + expect(exceptions.length, 3); + expect( + exceptions.first.toString(), + contains( + 'The merged cell containing (row: 0, column: 0) is ' + 'missing TableSpan information necessary for layout. The ' + 'rowBuilder returned null, signifying the end, at row 8 but ' + 'the merged cell is configured to end with row 9.', + ), + ); + }); + }); }); group('TableView.list', () { @@ -580,7 +2436,7 @@ void main() { await tester.pumpAndSettle(); // Even columns and rows are set up for taps, mainAxis is vertical by - // default, mening row major order. Rows should take precedence where they + // default, meaning row major order. Rows should take precedence where they // intersect at even indices. expect(columnTapCounter, 0); expect(rowTapCounter, 0); @@ -1730,6 +3586,550 @@ void main() { SystemMouseCursors.basic, ); }); + + group('Merged pinned cells layout', () { + // Regression tests for https://github.com/flutter/flutter/issues/143526 + // These tests all use the same collection of merged pinned cells in a + // variety of combinations. + final Map bothMerged = + { + TableVicinity.zero: (start: 0, span: 2), + const TableVicinity(row: 1, column: 0): (start: 0, span: 2), + const TableVicinity(row: 0, column: 1): (start: 0, span: 2), + const TableVicinity(row: 1, column: 1): (start: 0, span: 2), + }; + + final Map rowMerged = + { + const TableVicinity(row: 2, column: 0): (start: 2, span: 2), + const TableVicinity(row: 3, column: 0): (start: 2, span: 2), + const TableVicinity(row: 4, column: 1): (start: 4, span: 3), + const TableVicinity(row: 5, column: 1): (start: 4, span: 3), + const TableVicinity(row: 6, column: 1): (start: 4, span: 3), + }; + + final Map columnMerged = + { + const TableVicinity(row: 0, column: 2): (start: 2, span: 2), + const TableVicinity(row: 0, column: 3): (start: 2, span: 2), + const TableVicinity(row: 1, column: 4): (start: 4, span: 3), + const TableVicinity(row: 1, column: 5): (start: 4, span: 3), + const TableVicinity(row: 1, column: 6): (start: 4, span: 3), + }; + const TableSpan span = TableSpan(extent: FixedTableSpanExtent(75)); + + testWidgets('Normal axes', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + columnCount: 20, + rowCount: 20, + pinnedRowCount: 2, + pinnedColumnCount: 2, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return TableViewCell( + columnMergeStart: + bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start, + columnMergeSpan: + bothMerged[vicinity]?.span ?? columnMerged[vicinity]?.span, + rowMergeStart: + bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start, + rowMergeSpan: + bothMerged[vicinity]?.span ?? rowMerged[vicinity]?.span, + child: Text( + 'R${bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start ?? vicinity.row}:' + 'C${bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start ?? vicinity.column}', + ), + ); + }, + ); + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(150.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(300.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 150.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 300.0, 75.0, 225.0), + ); + + verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(150.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(300.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 140.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 290.0, 75.0, 225.0), + ); + + horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(140.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(290.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 140.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 290.0, 75.0, 225.0), + ); + }); + + testWidgets('Vertical reversed', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + verticalDetails: ScrollableDetails.vertical( + reverse: true, + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + columnCount: 20, + rowCount: 20, + pinnedRowCount: 2, + pinnedColumnCount: 2, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return TableViewCell( + columnMergeStart: + bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start, + columnMergeSpan: + bothMerged[vicinity]?.span ?? columnMerged[vicinity]?.span, + rowMergeStart: + bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start, + rowMergeSpan: + bothMerged[vicinity]?.span ?? rowMerged[vicinity]?.span, + child: Text( + 'R${bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start ?? vicinity.row}:' + 'C${bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start ?? vicinity.column}', + ), + ); + }, + ); + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(150.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(300.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 300.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 75.0, 75.0, 225.0), + ); + + verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(150.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(300.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 310.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 85.0, 75.0, 225.0), + ); + + horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(0.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(140.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(290.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(0.0, 310.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(75.0, 85.0, 75.0, 225.0), + ); + }); + + testWidgets('Horizontal reversed', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + reverse: true, + controller: horizontalController, + ), + columnCount: 20, + rowCount: 20, + pinnedRowCount: 2, + pinnedColumnCount: 2, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return TableViewCell( + columnMergeStart: + bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start, + columnMergeSpan: + bothMerged[vicinity]?.span ?? columnMerged[vicinity]?.span, + rowMergeStart: + bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start, + rowMergeSpan: + bothMerged[vicinity]?.span ?? rowMerged[vicinity]?.span, + child: Text( + 'R${bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start ?? vicinity.row}:' + 'C${bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start ?? vicinity.column}', + ), + ); + }, + ); + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(500.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(275.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 150.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 300.0, 75.0, 225.0), + ); + + verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(500.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(275.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 140.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 290.0, 75.0, 225.0), + ); + + horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 0.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(510.0, 0.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(285.0, 75.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 140.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 290.0, 75.0, 225.0), + ); + }); + + testWidgets('Both reversed', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + verticalDetails: ScrollableDetails.vertical( + reverse: true, + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + reverse: true, + controller: horizontalController, + ), + columnCount: 20, + rowCount: 20, + pinnedRowCount: 2, + pinnedColumnCount: 2, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return TableViewCell( + columnMergeStart: + bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start, + columnMergeSpan: + bothMerged[vicinity]?.span ?? columnMerged[vicinity]?.span, + rowMergeStart: + bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start, + rowMergeSpan: + bothMerged[vicinity]?.span ?? rowMerged[vicinity]?.span, + child: Text( + 'R${bothMerged[vicinity]?.start ?? rowMerged[vicinity]?.start ?? vicinity.row}:' + 'C${bothMerged[vicinity]?.start ?? columnMerged[vicinity]?.start ?? vicinity.column}', + ), + ); + }, + ); + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(500.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(275.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 300.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 75.0, 75.0, 225.0), + ); + + verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(500.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(275.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 310.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 85.0, 75.0, 225.0), + ); + + horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + expect( + tester.getRect(find.text('R0:C0')), + const Rect.fromLTWH(650.0, 450.0, 150.0, 150.0), + ); + expect( + tester.getRect(find.text('R0:C2')), + const Rect.fromLTWH(510.0, 525.0, 150.0, 75.0), + ); + expect( + tester.getRect(find.text('R1:C4')), + const Rect.fromLTWH(285.0, 450.0, 225.0, 75.0), + ); + expect( + tester.getRect(find.text('R2:C0')), + const Rect.fromLTWH(725.0, 310.0, 75.0, 150.0), + ); + expect( + tester.getRect(find.text('R4:C1')), + const Rect.fromLTWH(650.0, 85.0, 75.0, 225.0), + ); + }); + }); + }); + + testWidgets( + 'Merged unpinned cells following pinned cells are laid out correctly', + (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final Set mergedCell = { + const TableVicinity(row: 2, column: 2), + const TableVicinity(row: 3, column: 2), + const TableVicinity(row: 2, column: 3), + const TableVicinity(row: 3, column: 3), + }; + final TableView tableView = TableView.builder( + columnCount: 10, + rowCount: 10, + columnBuilder: (_) => const TableSpan(extent: FixedTableSpanExtent(100)), + rowBuilder: (_) => const TableSpan(extent: FixedTableSpanExtent(100)), + cellBuilder: (BuildContext context, TableVicinity vicinity) { + if (mergedCell.contains(vicinity)) { + return const TableViewCell( + rowMergeStart: 2, + rowMergeSpan: 2, + columnMergeStart: 2, + columnMergeSpan: 2, + child: Text('Tile c: 2, r: 2'), + ); + } + return TableViewCell( + child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'), + ); + }, + pinnedRowCount: 1, + pinnedColumnCount: 1, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('Tile c: 2, r: 2')), + const Rect.fromLTWH(200.0, 200.0, 200.0, 200.0), + ); + + verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + expect( + tester.getRect(find.text('Tile c: 2, r: 2')), + const Rect.fromLTWH(200.0, 190.0, 200.0, 200.0), + ); + + horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + expect( + tester.getRect(find.text('Tile c: 2, r: 2')), + const Rect.fromLTWH(190.0, 190.0, 200.0, 200.0), + ); }); } diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 61a568c9aa1c..3659a50c0a6e 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.2.5 + +* Removes use of deprecated `renderView` API. + ## 6.2.4 * Updates support matrix in README to indicate that iOS 11 is no longer supported. diff --git a/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt index c7a8c7607d81..c25ffc272ada 100644 --- a/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt +++ b/packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -91,6 +96,7 @@ add_custom_command( ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ + VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" diff --git a/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc b/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc index dbda44723259..4d7a2dbcdc20 100644 --- a/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc +++ b/packages/url_launcher/url_launcher/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart index 9f6d2dca001e..f709a75f758a 100644 --- a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart +++ b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart @@ -3,8 +3,10 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:ui'; import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; @@ -85,15 +87,14 @@ Future launch( /// [true] so that ui is automatically computed if [statusBarBrightness] is set. bool previousAutomaticSystemUiAdjustment = true; - if (statusBarBrightness != null && - defaultTargetPlatform == TargetPlatform.iOS && - _ambiguate(WidgetsBinding.instance) != null) { - previousAutomaticSystemUiAdjustment = _ambiguate(WidgetsBinding.instance)! - .renderView - .automaticSystemUiAdjustment; - _ambiguate(WidgetsBinding.instance)! - .renderView - .automaticSystemUiAdjustment = false; + final RenderView? renderViewToAdjust = + statusBarBrightness != null && defaultTargetPlatform == TargetPlatform.iOS + ? _findImplicitRenderView() + : null; + if (renderViewToAdjust != null) { + previousAutomaticSystemUiAdjustment = + renderViewToAdjust.automaticSystemUiAdjustment; + renderViewToAdjust.automaticSystemUiAdjustment = false; SystemChrome.setSystemUIOverlayStyle(statusBarBrightness == Brightness.light ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light); @@ -110,11 +111,9 @@ Future launch( webOnlyWindowName: webOnlyWindowName, ); - if (statusBarBrightness != null && - _ambiguate(WidgetsBinding.instance) != null) { - _ambiguate(WidgetsBinding.instance)! - .renderView - .automaticSystemUiAdjustment = previousAutomaticSystemUiAdjustment; + if (renderViewToAdjust != null) { + renderViewToAdjust.automaticSystemUiAdjustment = + previousAutomaticSystemUiAdjustment; } return result; @@ -146,8 +145,22 @@ Future closeWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } -/// This allows a value of type T or T? to be treated as a value of type T?. +/// Returns the [RenderView] associated with the implicit [FlutterView], if any. /// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; +/// [launch] predates multi-window support, and it doesn't have enough context +/// to get the right render view, so this assumes anyone still trying to use +/// the deprecated API with `statusBarBrightness` is in a single-view scenario. +/// This allows a best-effort implementation of the deprecated API for as long +/// as it continues to exist, without depending on deprecated Flutter APIs (and +/// therefore keeping url_launcher forward-compatible with future versions of +/// Flutter for longer). +RenderView? _findImplicitRenderView() { + final FlutterView? implicitFlutterView = + WidgetsBinding.instance.platformDispatcher.implicitView; + if (implicitFlutterView == null) { + return null; + } + return WidgetsBinding.instance.renderViews + .where((RenderView v) => v.flutterView == implicitFlutterView) + .firstOrNull; +} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index fe3f2990d08a..e59afe4690da 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.4 +version: 6.2.5 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart index 8a694546397c..091f9b7c8fd2 100644 --- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart +++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart @@ -235,10 +235,11 @@ void main() { ..setResponse(true); final TestWidgetsFlutterBinding binding = - _anonymize(TestWidgetsFlutterBinding.ensureInitialized())! - as TestWidgetsFlutterBinding; + TestWidgetsFlutterBinding.ensureInitialized(); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - final RenderView renderView = binding.renderView; + final RenderView renderView = + RenderView(view: binding.platformDispatcher.implicitView!); + binding.addRenderView(renderView); renderView.automaticSystemUiAdjustment = true; final Future launchResult = launch('http://flutter.dev/', statusBarBrightness: Brightness.dark); @@ -248,6 +249,7 @@ void main() { expect(renderView.automaticSystemUiAdjustment, isFalse); await launchResult; expect(renderView.automaticSystemUiAdjustment, isTrue); + binding.removeRenderView(renderView); }); test('sets automaticSystemUiAdjustment to not be null', () async { @@ -265,10 +267,11 @@ void main() { ..setResponse(true); final TestWidgetsFlutterBinding binding = - _anonymize(TestWidgetsFlutterBinding.ensureInitialized())! - as TestWidgetsFlutterBinding; + TestWidgetsFlutterBinding.ensureInitialized(); debugDefaultTargetPlatformOverride = TargetPlatform.android; - final RenderView renderView = binding.renderView; + final RenderView renderView = + RenderView(view: binding.platformDispatcher.implicitView!); + binding.addRenderView(renderView); expect(renderView.automaticSystemUiAdjustment, true); final Future launchResult = launch('http://flutter.dev/', statusBarBrightness: Brightness.dark); @@ -278,6 +281,7 @@ void main() { expect(renderView.automaticSystemUiAdjustment, true); await launchResult; expect(renderView.automaticSystemUiAdjustment, true); + binding.removeRenderView(renderView); }); test('open non-parseable url', () async { @@ -317,9 +321,3 @@ void main() { }); }); } - -/// This removes the type information from a value so that it can be cast -/// to another type even if that cast is redundant. -/// We use this so that APIs whose type have become more descriptive can still -/// be used on the stable branch where they require a cast. -Object? _anonymize(T? value) => value; diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index 7ff9aa669147..b5bb9c81481b 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 6.2.5 +* Adds explicit imports for UIKit. * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. ## 6.2.4 diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift index f97db9db9c5e..454dd53fbb9c 100644 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import UIKit + /// Protocol for UIApplication methods relating to launching URLs. /// /// This protocol exists to allow injecting an alternate implementation for testing. diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift index 18800319218e..44718dee7323 100644 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift @@ -3,6 +3,7 @@ // found in the LICENSE file. import Flutter +import UIKit public final class URLLauncherPlugin: NSObject, FlutterPlugin, UrlLauncherApi { diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 6be514eb8f0e..fd8c54619c55 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.4 +version: 6.2.5 environment: sdk: ^3.2.3 diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 641b48d27cf2..5e3d6d42b35e 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0 + +* Updates web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. + ## 2.2.3 * Fixes new lint warnings. diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart index 528cff32c0e5..9cd76d03c80c 100644 --- a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart +++ b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:js_util'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'dart:ui_web' as ui_web; import 'package:flutter/widgets.dart'; @@ -10,7 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_web/src/link.dart'; -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -177,7 +178,7 @@ html.Element _findSingleAnchor() { html.NodeList anchors = html.document.querySelectorAll('a'); for (int i = 0; i < anchors.length; i++) { final html.Element anchor = anchors.item(i)! as html.Element; - if (hasProperty(anchor, linkViewIdProperty)) { + if (anchor.hasProperty(linkViewIdProperty.toJS).toDart) { foundAnchors.add(anchor); } } @@ -189,7 +190,7 @@ html.Element _findSingleAnchor() { anchors = shadowRoot.querySelectorAll('a'); for (int i = 0; i < anchors.length; i++) { final html.Element anchor = anchors.item(i)! as html.Element; - if (hasProperty(anchor, linkViewIdProperty)) { + if (anchor.hasProperty(linkViewIdProperty.toJS).toDart) { foundAnchors.add(anchor); } } diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart index ec01d28163aa..b75903873467 100644 --- a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart +++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart @@ -3,14 +3,13 @@ // found in the LICENSE file. import 'dart:js_interop'; -import 'dart:js_util'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/mockito.dart' show Mock, any, verify, when; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; abstract class MyWindow { html.Window? open(Object? a, Object? b, Object? c); @@ -33,6 +32,7 @@ void main() { group('UrlLauncherPlugin', () { late MockWindow mockWindow; late MockNavigator mockNavigator; + late html.Window jsMockWindow; late UrlLauncherPlugin plugin; @@ -40,10 +40,9 @@ void main() { mockWindow = MockWindow(); mockNavigator = MockNavigator(); - final html.Window jsMockWindow = - createDartExport(mockWindow) as html.Window; + jsMockWindow = createJSInteropWrapper(mockWindow) as html.Window; final html.Navigator jsMockNavigator = - createDartExport(mockNavigator) as html.Navigator; + createJSInteropWrapper(mockNavigator) as html.Navigator; when(mockWindow.navigator).thenReturn(jsMockNavigator); @@ -53,7 +52,7 @@ void main() { when(mockNavigator.userAgent).thenReturn( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); - plugin = UrlLauncherPlugin(debugWindow: mockWindow as html.Window); + plugin = UrlLauncherPlugin(debugWindow: jsMockWindow); }); group('canLaunch', () { @@ -185,7 +184,7 @@ void main() { when(mockNavigator.userAgent).thenReturn( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.1 Safari/605.1.15'); // Recreate the plugin, so it grabs the overrides from this group - plugin = UrlLauncherPlugin(debugWindow: mockWindow as html.Window); + plugin = UrlLauncherPlugin(debugWindow: jsMockWindow); }); testWidgets('http urls should be launched in a new window', diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index eca281ef0a2f..c87511dd84b8 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -2,8 +2,8 @@ name: regular_integration_tests publish_to: none environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: @@ -18,4 +18,4 @@ dev_dependencies: url_launcher_platform_interface: ^2.2.0 url_launcher_web: path: ../ - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 diff --git a/packages/url_launcher/url_launcher_web/lib/src/link.dart b/packages/url_launcher/url_launcher_web/lib/src/link.dart index b2217fafb805..558f3a135a27 100644 --- a/packages/url_launcher/url_launcher_web/lib/src/link.dart +++ b/packages/url_launcher/url_launcher_web/lib/src/link.dart @@ -3,7 +3,8 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:js_util'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'dart:ui_web' as ui_web; import 'package:flutter/foundation.dart'; @@ -12,7 +13,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:url_launcher_platform_interface/link.dart'; -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; /// The unique identifier for the view type to be used for link platform views. const String linkViewType = '__url_launcher::link'; @@ -172,7 +173,7 @@ class LinkViewController extends PlatformViewController { Future _initialize() async { _element = html.document.createElement('a') as html.HTMLElement; - setProperty(_element, linkViewIdProperty, viewId); + _element[linkViewIdProperty] = viewId.toJS; _element.style ..opacity = '0' ..display = 'block' @@ -283,11 +284,7 @@ class LinkViewController extends PlatformViewController { int? getViewIdFromTarget(html.Event event) { final html.Element? linkElement = getLinkElementFromTarget(event); if (linkElement != null) { - // TODO(stuartmorgan): Remove this ignore (and change to getProperty) - // once the templated version is available on stable. On master (2.8) this - // is already not necessary. - // ignore: return_of_invalid_type - return getProperty(linkElement, linkViewIdProperty); + return linkElement.getProperty(linkViewIdProperty.toJS).toDartInt; } return null; } @@ -316,5 +313,5 @@ html.Element? getLinkElementFromTarget(html.Event event) { bool isLinkElement(html.Element? element) { return element != null && element.tagName == 'A' && - hasProperty(element, linkViewIdProperty); + element.hasProperty(linkViewIdProperty.toJS).toDart; } diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 6c89bba58091..35154b5a6d2b 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_web description: Web platform implementation of url_launcher repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.2.3 +version: 2.3.0 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,7 +22,7 @@ dependencies: flutter_web_plugins: sdk: flutter url_launcher_platform_interface: ^2.2.0 - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt index 744f08a9389b..0a9177722722 100644 --- a/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt +++ b/packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -90,7 +95,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc b/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc index 944329afc03a..41d1b5cf736b 100644 --- a/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc +++ b/packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#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 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index c585875c22eb..cb872ce7c9a9 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.8.3 +* Fixes typo in `README.md`. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates support matrix in README to indicate that iOS 11 is no longer supported. * Clients on versions of Flutter that still support iOS 11 can continue to use this diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index 99a6c0a9795b..4d39cec336e7 100644 --- a/packages/video_player/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -41,7 +41,7 @@ entitlement](https://docs.flutter.dev/platform-integration/macos/building#entitl ### Web -> The Web platform does **not** suppport `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using the constructor attempts to create a `VideoPlayerController.file` that will throw an `UnimplementedError`. +> The Web platform does **not** support `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using the constructor attempts to create a `VideoPlayerController.file` that will throw an `UnimplementedError`. \* Different web browsers may have different video-playback capabilities (supported formats, autoplay...). Check [package:video_player_web](https://pub.dev/packages/video_player_web) for more web-specific information. diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 1b169ff64a42..7b128216f415 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.8.2 +version: 2.8.3 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h b/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h index a09af39a90ef..30dab06f18f2 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h @@ -4,11 +4,9 @@ #import -/** - * Returns a standardized transform - * according to the orientation of the track. - * - * Note: https://stackoverflow.com/questions/64161544 - * `AVAssetTrack.preferredTransform` can have wrong `tx` and `ty`. - */ +/// Returns a standardized transform +/// according to the orientation of the track. +/// +/// Note: https://stackoverflow.com/questions/64161544 +/// `AVAssetTrack.preferredTransform` can have wrong `tx` and `ty`. CGAffineTransform FVPGetStandardizedTransformForTrack(AVAssetTrack* track); diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h b/packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h index 67c0bf75f3f5..80d400629b25 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h @@ -13,19 +13,15 @@ // A cross-platform display link abstraction. @interface FVPDisplayLink : NSObject -/** - * Whether the display link is currently running (i.e., firing events). - * - * Defaults to NO. - */ +/// Whether the display link is currently running (i.e., firing events). +/// +/// Defaults to NO. @property(nonatomic, assign) BOOL running; -/** - * Initializes a display link that calls the given callback when fired. - * - * The display link starts paused, so must be started, by setting 'running' to YES, before the - * callback will fire. - */ +/// Initializes a display link that calls the given callback when fired. +/// +/// The display link starts paused, so must be started, by setting 'running' to YES, before the +/// callback will fire. - (instancetype)initWithRegistrar:(id)registrar callback:(void (^)(void))callback NS_DESIGNATED_INITIALIZER; diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m b/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m index 70ede124f8e6..d90f57d91dec 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m @@ -72,7 +72,7 @@ - (AVPlayerItemVideoOutput *)videoOutputWithPixelBufferAttributes: } @end -/** Non-test implementation of the diplay link factory. */ +/// Non-test implementation of the diplay link factory. @interface FVPDefaultDisplayLinkFactory : NSObject @end diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m b/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m index 51039067f38c..505001bc223f 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m @@ -7,17 +7,15 @@ #import #import -/** - * A proxy object to act as a CADisplayLink target, to avoid retain loops, since FVPDisplayLink - * owns its CADisplayLink, but CADisplayLink retains its target. - */ +/// A proxy object to act as a CADisplayLink target, to avoid retain loops, since FVPDisplayLink +/// owns its CADisplayLink, but CADisplayLink retains its target. @interface FVPDisplayLinkTarget : NSObject @property(nonatomic) void (^callback)(void); -/** Initializes a target object that runs the given callback when onDisplayLink: is called. */ +/// Initializes a target object that runs the given callback when onDisplayLink: is called. - (instancetype)initWithCallback:(void (^)(void))callback; -/** Method to be called when a CADisplayLink fires. */ +/// Method to be called when a CADisplayLink fires. - (void)onDisplayLink:(CADisplayLink *)link; @end diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 4ce1c96de5d8..cac6a397996f 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT +## 2.3.0 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Migrates package and tests to `package:web``. +* Fixes infinite event loop caused by `seekTo` when the video ends. + +## 2.2.0 + +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. ## 2.1.3 diff --git a/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart new file mode 100644 index 000000000000..ccf5b32e5c65 --- /dev/null +++ b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@JS() +library video_player_web_integration_test_pkg_web_tweaks; + +import 'dart:js_interop'; +import 'package:web/web.dart' as web; + +/// Adds a `controlsList` and `disablePictureInPicture` getters. +extension NonStandardGettersOnVideoElement on web.HTMLVideoElement { + external web.DOMTokenList? get controlsList; + external JSBoolean get disablePictureInPicture; +} + +/// Adds a `disableRemotePlayback` getter. +extension NonStandardGettersOnMediaElement on web.HTMLMediaElement { + external JSBoolean get disableRemotePlayback; +} + +/// Defines JS interop to access static methods from `Object`. +@JS('Object') +extension type DomObject._(JSAny _) { + @JS('defineProperty') + external static void _defineProperty( + JSAny? object, JSString property, Descriptor value); + + /// `Object.defineProperty`. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty + static void defineProperty( + JSObject object, String property, Descriptor descriptor) { + return _defineProperty(object, property.toJS, descriptor); + } +} + +/// The descriptor for the property being defined or modified with `defineProperty`. +/// +/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description +extension type Descriptor._(JSObject _) implements JSObject { + /// Builds a "data descriptor". + factory Descriptor.data({ + bool? writable, + JSAny? value, + }) => + Descriptor._data( + writable: writable?.toJS, + value: value.jsify(), + ); + + /// Builds an "accessor descriptor". + factory Descriptor.accessor({ + void Function(JSAny? value)? set, + JSAny? Function()? get, + }) => + Descriptor._accessor( + set: set?.toJS, + get: get?.toJS, + ); + + external factory Descriptor._accessor({ + // JSBoolean configurable, + // JSBoolean enumerable, + JSFunction? set, + JSFunction? get, + }); + + external factory Descriptor._data({ + // JSBoolean configurable, + // JSBoolean enumerable, + JSBoolean? writable, + JSAny? value, + }); +} diff --git a/packages/video_player/video_player_web/example/integration_test/utils.dart b/packages/video_player/video_player_web/example/integration_test/utils.dart index 6be5b5abcc75..ba15c39c57d6 100644 --- a/packages/video_player/video_player_web/example/integration_test/utils.dart +++ b/packages/video_player/video_player_web/example/integration_test/utils.dart @@ -2,12 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@JS() -library integration_test_utils; +import 'dart:js_interop'; -import 'dart:html'; - -import 'package:js/js.dart'; +import 'package:web/web.dart' as web; +import 'pkg_web_tweaks.dart'; // Returns the URL to load an asset from this example app as a network source. // @@ -22,35 +20,29 @@ String getUrlForAssetAsNetworkSource(String assetKey) { '?raw=true'; } -@JS() -@anonymous -class _Descriptor { - // May also contain "configurable" and "enumerable" bools. - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description - external factory _Descriptor({ - // bool configurable, - // bool enumerable, - bool writable, - Object value, - }); -} - -@JS('Object.defineProperty') -external void _defineProperty( - Object object, - String property, - _Descriptor description, -); - /// Forces a VideoElement to report "Infinity" duration. /// /// Uses JS Object.defineProperty to set the value of a readonly property. -void setInfinityDuration(VideoElement element) { - _defineProperty( +void setInfinityDuration(web.HTMLVideoElement element) { + DomObject.defineProperty( + element, + 'duration', + Descriptor.data( + writable: true, + value: double.infinity.toJS, + ), + ); +} + +/// Makes the `currentTime` setter throw an exception if used. +void makeSetCurrentTimeThrow(web.HTMLVideoElement element) { + DomObject.defineProperty( element, - 'duration', - _Descriptor( - writable: true, - value: double.infinity, + 'currentTime', + Descriptor.accessor( + set: (JSAny? value) { + throw Exception('Unexpected call to currentTime with value: $value'); + }, + get: () => 100.toJS, )); } diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart index 01f8e2f34357..51199ba79d2b 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart @@ -3,27 +3,28 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'package:video_player_web/src/duration_utils.dart'; import 'package:video_player_web/src/video_player.dart'; +import 'package:web/web.dart' as web; +import 'pkg_web_tweaks.dart'; import 'utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('VideoPlayer', () { - late html.VideoElement video; + late web.HTMLVideoElement video; setUp(() { // Never set "src" on the video, so this test doesn't hit the network! - video = html.VideoElement() + video = web.HTMLVideoElement() ..controls = true - ..setAttribute('playsinline', 'false'); + ..playsInline = false; }); testWidgets('fixes critical video element config', (WidgetTester _) async { @@ -36,8 +37,7 @@ void main() { // see: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML expect(video.getAttribute('autoplay'), isNull, reason: 'autoplay attribute on video tag must NOT be set'); - expect(video.getAttribute('playsinline'), 'true', - reason: 'Needed by safari iOS'); + expect(video.playsInline, true, reason: 'Needed by safari iOS'); }); testWidgets('setVolume', (WidgetTester tester) async { @@ -69,12 +69,32 @@ void main() { }, throwsAssertionError, reason: 'Playback speed cannot be == 0'); }); - testWidgets('seekTo', (WidgetTester tester) async { - final VideoPlayer player = VideoPlayer(videoElement: video)..initialize(); + group('seekTo', () { + testWidgets('negative time - throws assert', (WidgetTester tester) async { + final VideoPlayer player = VideoPlayer(videoElement: video) + ..initialize(); - expect(() { - player.seekTo(const Duration(seconds: -1)); - }, throwsAssertionError, reason: 'Cannot seek into negative numbers'); + expect(() { + player.seekTo(const Duration(seconds: -1)); + }, throwsAssertionError, reason: 'Cannot seek into negative numbers'); + }); + + testWidgets('setting currentTime to its current value - noop', + (WidgetTester tester) async { + makeSetCurrentTimeThrow(video); + final VideoPlayer player = VideoPlayer(videoElement: video) + ..initialize(); + + expect(() { + // Self-test... + video.currentTime = 123; + }, throwsException, reason: 'Setting currentTime must throw!'); + + expect(() { + // Should not set currentTime (and throw) when seekTo current time. + player.seekTo(Duration(seconds: video.currentTime.toInt())); + }, returnsNormally); + }); }); // The events tested in this group do *not* represent the actual sequence @@ -145,7 +165,7 @@ void main() { player.setBuffering(true); // Simulate "canplay" event... - video.dispatchEvent(html.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); final List events = await stream; @@ -166,7 +186,7 @@ void main() { player.setBuffering(true); // Simulate "canplaythrough" event... - video.dispatchEvent(html.Event('canplaythrough')); + video.dispatchEvent(web.Event('canplaythrough')); final List events = await stream; @@ -177,9 +197,9 @@ void main() { testWidgets('initialized dispatches only once', (WidgetTester tester) async { // Dispatch some bogus "canplay" events from the video object - video.dispatchEvent(html.Event('canplay')); - video.dispatchEvent(html.Event('canplay')); - video.dispatchEvent(html.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); // Take all the "initialized" events that we see during the next few seconds final Future> stream = timedStream @@ -187,9 +207,9 @@ void main() { event.eventType == VideoEventType.initialized) .toList(); - video.dispatchEvent(html.Event('canplay')); - video.dispatchEvent(html.Event('canplay')); - video.dispatchEvent(html.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); final List events = await stream; @@ -200,8 +220,8 @@ void main() { // Issue: https://github.com/flutter/flutter/issues/137023 testWidgets('loadedmetadata dispatches initialized', (WidgetTester tester) async { - video.dispatchEvent(html.Event('loadedmetadata')); - video.dispatchEvent(html.Event('loadedmetadata')); + video.dispatchEvent(web.Event('loadedmetadata')); + video.dispatchEvent(web.Event('loadedmetadata')); final Future> stream = timedStream .where((VideoEvent event) => @@ -224,7 +244,7 @@ void main() { event.eventType == VideoEventType.initialized) .toList(); - video.dispatchEvent(html.Event('canplay')); + video.dispatchEvent(web.Event('canplay')); final List events = await stream; @@ -238,7 +258,7 @@ void main() { late VideoPlayer player; setUp(() { - video = html.VideoElement(); + video = web.HTMLVideoElement(); player = VideoPlayer(videoElement: video)..initialize(); }); @@ -271,7 +291,7 @@ void main() { expect(video.controlsList?.contains('nodownload'), isFalse); expect(video.controlsList?.contains('nofullscreen'), isFalse); expect(video.controlsList?.contains('noplaybackrate'), isFalse); - expect(video.getAttribute('disablePictureInPicture'), isNull); + expect(video.disablePictureInPicture, isFalse); }); testWidgets('and no download expect correct controls', @@ -290,7 +310,7 @@ void main() { expect(video.controlsList?.contains('nodownload'), isTrue); expect(video.controlsList?.contains('nofullscreen'), isFalse); expect(video.controlsList?.contains('noplaybackrate'), isFalse); - expect(video.getAttribute('disablePictureInPicture'), isNull); + expect(video.disablePictureInPicture, isFalse); }); testWidgets('and no fullscreen expect correct controls', @@ -309,7 +329,7 @@ void main() { expect(video.controlsList?.contains('nodownload'), isFalse); expect(video.controlsList?.contains('nofullscreen'), isTrue); expect(video.controlsList?.contains('noplaybackrate'), isFalse); - expect(video.getAttribute('disablePictureInPicture'), isNull); + expect(video.disablePictureInPicture, isFalse); }); testWidgets('and no playback rate expect correct controls', @@ -328,7 +348,7 @@ void main() { expect(video.controlsList?.contains('nodownload'), isFalse); expect(video.controlsList?.contains('nofullscreen'), isFalse); expect(video.controlsList?.contains('noplaybackrate'), isTrue); - expect(video.getAttribute('disablePictureInPicture'), isNull); + expect(video.disablePictureInPicture, isFalse); }); testWidgets('and no picture in picture expect correct controls', @@ -347,7 +367,7 @@ void main() { expect(video.controlsList?.contains('nodownload'), isFalse); expect(video.controlsList?.contains('nofullscreen'), isFalse); expect(video.controlsList?.contains('noplaybackrate'), isFalse); - expect(video.getAttribute('disablePictureInPicture'), 'true'); + expect(video.disablePictureInPicture, isTrue); }); }); }); @@ -362,7 +382,7 @@ void main() { ), ); - expect(video.getAttribute('disableRemotePlayback'), isNull); + expect(video.disableRemotePlayback, isFalse); }); testWidgets('when disabled expect attribute', @@ -373,7 +393,7 @@ void main() { ), ); - expect(video.getAttribute('disableRemotePlayback'), 'true'); + expect(video.disableRemotePlayback, isTrue); }); }); @@ -398,8 +418,8 @@ void main() { expect(video.controlsList?.contains('nodownload'), isTrue); expect(video.controlsList?.contains('nofullscreen'), isTrue); expect(video.controlsList?.contains('noplaybackrate'), isTrue); - expect(video.getAttribute('disablePictureInPicture'), 'true'); - expect(video.getAttribute('disableRemotePlayback'), 'true'); + expect(video.disablePictureInPicture, isTrue); + expect(video.disableRemotePlayback, isTrue); }); group('when called once more', () { @@ -421,8 +441,8 @@ void main() { expect(video.controlsList?.contains('nodownload'), isFalse); expect(video.controlsList?.contains('nofullscreen'), isFalse); expect(video.controlsList?.contains('noplaybackrate'), isFalse); - expect(video.getAttribute('disablePictureInPicture'), isNull); - expect(video.getAttribute('disableRemotePlayback'), isNull); + expect(video.disablePictureInPicture, isFalse); + expect(video.disableRemotePlayback, isFalse); }); }); }); diff --git a/packages/video_player/video_player_web/example/pubspec.yaml b/packages/video_player/video_player_web/example/pubspec.yaml index 866d089f8687..27aba7660bb8 100644 --- a/packages/video_player/video_player_web/example/pubspec.yaml +++ b/packages/video_player/video_player_web/example/pubspec.yaml @@ -2,16 +2,16 @@ name: video_player_for_web_integration_tests publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: sdk: flutter - js: ^0.6.0 - video_player_platform_interface: ">=6.1.0 <7.0.0" + video_player_platform_interface: ^6.1.0 video_player_web: path: ../ + web: ^0.5.1 dev_dependencies: flutter_test: diff --git a/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart b/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart new file mode 100644 index 000000000000..c0ae661c96b2 --- /dev/null +++ b/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:js_interop'; +import 'package:web/web.dart' as web; + +/// Adds a "disablePictureInPicture" setter to [web.HTMLVideoElement]s. +extension NonStandardSettersOnVideoElement on web.HTMLVideoElement { + external set disablePictureInPicture(JSBoolean disabled); +} + +/// Adds a "disableRemotePlayback" and "controlsList" setters to [web.HTMLMediaElement]s. +extension NonStandardSettersOnMediaElement on web.HTMLMediaElement { + external set disableRemotePlayback(JSBoolean disabled); + external set controlsList(JSString? controlsList); +} diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 4adb2e1e8662..012463fc780d 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -3,13 +3,16 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'package:web/helpers.dart'; +import 'package:web/web.dart' as web; import 'duration_utils.dart'; +import 'pkg_web_tweaks.dart'; // An error code value to error name Map. // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code @@ -34,29 +37,29 @@ const Map _kErrorValueToErrorDescription = { const String _kDefaultErrorMessage = 'No further diagnostic information can be determined or provided.'; -/// Wraps a [html.VideoElement] so its API complies with what is expected by the plugin. +/// Wraps a [web.HTMLVideoElement] so its API complies with what is expected by the plugin. class VideoPlayer { - /// Create a [VideoPlayer] from a [html.VideoElement] instance. + /// Create a [VideoPlayer] from a [web.HTMLVideoElement] instance. VideoPlayer({ - required html.VideoElement videoElement, + required web.HTMLVideoElement videoElement, @visibleForTesting StreamController? eventController, }) : _videoElement = videoElement, _eventController = eventController ?? StreamController(); final StreamController _eventController; - final html.VideoElement _videoElement; - void Function(html.Event)? _onContextMenu; + final web.HTMLVideoElement _videoElement; + web.EventHandler? _onContextMenu; bool _isInitialized = false; bool _isBuffering = false; - /// Returns the [Stream] of [VideoEvent]s from the inner [html.VideoElement]. + /// Returns the [Stream] of [VideoEvent]s from the inner [web.HTMLVideoElement]. Stream get events => _eventController.stream; - /// Initializes the wrapped [html.VideoElement]. + /// Initializes the wrapped [web.HTMLVideoElement]. /// /// This method sets the required DOM attributes so videos can [play] programmatically, - /// and attaches listeners to the internal events from the [html.VideoElement] + /// and attaches listeners to the internal events from the [web.HTMLVideoElement] /// to react to them / expose them through the [VideoPlayer.events] stream. /// /// The [src] parameter is the URL of the video. It is passed in from the plugin @@ -71,14 +74,8 @@ class VideoPlayer { }) { _videoElement ..autoplay = false - ..controls = false; - - // Allows Safari iOS to play the video inline. - // - // This property is not exposed through dart:html so we use the - // HTML Boolean attribute form (when present with any value => true) - // See: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML - _videoElement.setAttribute('playsinline', true); + ..controls = false + ..playsInline = true; _videoElement.onCanPlay.listen(_onVideoElementInitialization); // Needed for Safari iOS 17, which may not send `canplay`. @@ -98,12 +95,12 @@ class VideoPlayer { }); // The error event fires when some form of error occurs while attempting to load or perform the media. - _videoElement.onError.listen((html.Event _) { + _videoElement.onError.listen((web.Event _) { setBuffering(false); // The Event itself (_) doesn't contain info about the actual error. // We need to look at the HTMLMediaElement.error. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/error - final html.MediaError error = _videoElement.error!; + final web.MediaError error = _videoElement.error!; _eventController.addError(PlatformException( code: _kErrorValueToErrorName[error.code]!, message: error.message != '' ? error.message : _kDefaultErrorMessage, @@ -145,18 +142,19 @@ class VideoPlayer { /// When called from some user interaction (a tap on a button), the above /// limitation should disappear. Future play() { - return _videoElement.play().catchError((Object e) { + return _videoElement.play().toDart.catchError((Object e) { // play() attempts to begin playback of the media. It returns // a Promise which can get rejected in case of failure to begin // playback for any reason, such as permission issues. - // The rejection handler is called with a DomException. + // The rejection handler is called with a DOMException. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play - final html.DomException exception = e as html.DomException; + final web.DOMException exception = e as web.DOMException; _eventController.addError(PlatformException( code: exception.name, message: exception.message, )); - }, test: (Object e) => e is html.DomException); + return null; + }, test: (Object e) => e is web.DOMException); } /// Pauses the video in the current position. @@ -175,7 +173,7 @@ class VideoPlayer { /// Values must fall between 0 and 1, where 0 is muted and 1 is the loudest. /// /// When volume is set to 0, the `muted` property is also applied to the - /// [html.VideoElement]. This is required for auto-play on the web. + /// [web.HTMLVideoElement]. This is required for auto-play on the web. void setVolume(double volume) { assert(volume >= 0 && volume <= 1); @@ -208,12 +206,28 @@ class VideoPlayer { void seekTo(Duration position) { assert(!position.isNegative); + // Don't seek if video is already at target position. + // + // This is needed because the core plugin will pause and seek to the end of + // the video when it finishes, and that causes an infinite loop of `ended` + // events on the web. + // + // See: https://github.com/flutter/flutter/issues/77674 + if (position == _videoElementCurrentTime) { + return; + } + _videoElement.currentTime = position.inMilliseconds.toDouble() / 1000; } /// Returns the current playback head position as a [Duration]. Duration getPosition() { _sendBufferingRangesUpdate(); + return _videoElementCurrentTime; + } + + /// Returns the currentTime of the underlying video element. + Duration get _videoElementCurrentTime { return Duration(milliseconds: (_videoElement.currentTime * 1000).round()); } @@ -226,21 +240,21 @@ class VideoPlayer { _videoElement.controls = true; final String controlsList = options.controls.controlsList; if (controlsList.isNotEmpty) { - _videoElement.setAttribute('controlsList', controlsList); + _videoElement.controlsList = controlsList.toJS; } if (!options.controls.allowPictureInPicture) { - _videoElement.setAttribute('disablePictureInPicture', true); + _videoElement.disablePictureInPicture = true.toJS; } } if (!options.allowContextMenu) { - _onContextMenu = (html.Event event) => event.preventDefault(); + _onContextMenu = ((web.Event event) => event.preventDefault()).toJS; _videoElement.addEventListener('contextmenu', _onContextMenu); } if (!options.allowRemotePlayback) { - _videoElement.setAttribute('disableRemotePlayback', true); + _videoElement.disableRemotePlayback = true.toJS; } } @@ -255,7 +269,7 @@ class VideoPlayer { _videoElement.removeAttribute('disableRemotePlayback'); } - /// Disposes of the current [html.VideoElement]. + /// Disposes of the current [web.HTMLVideoElement]. void dispose() { _videoElement.removeAttribute('src'); if (_onContextMenu != null) { @@ -316,7 +330,7 @@ class VideoPlayer { } } - // Broadcasts the [html.VideoElement.buffered] status through the [events] stream. + // Broadcasts the [web.HTMLVideoElement.buffered] status through the [events] stream. void _sendBufferingRangesUpdate() { _eventController.add(VideoEvent( buffered: _toDurationRange(_videoElement.buffered), @@ -325,7 +339,7 @@ class VideoPlayer { } // Converts from [html.TimeRanges] to our own List. - List _toDurationRange(html.TimeRanges buffered) { + List _toDurationRange(web.TimeRanges buffered) { final List durationRange = []; for (int i = 0; i < buffered.length; i++) { durationRange.add(DurationRange( diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 67cf2746977d..8f5c0265e965 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -3,12 +3,12 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html'; import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'package:web/web.dart' as web; import 'src/video_player.dart'; @@ -71,7 +71,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { 'web implementation of video_player cannot play content uri')); } - final VideoElement videoElement = VideoElement() + final web.HTMLVideoElement videoElement = web.HTMLVideoElement() ..id = 'videoElement-$textureId' ..style.border = 'none' ..style.height = '100%' diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 81ad0e6d1bf7..dd876328b014 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -2,11 +2,11 @@ name: video_player_web description: Web platform implementation of video_player. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.1.3 +version: 2.3.0 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -22,6 +22,7 @@ dependencies: flutter_web_plugins: sdk: flutter video_player_platform_interface: ^6.2.0 + web: ^0.5.1 dev_dependencies: flutter_test: diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md index 98e9f675b1fd..02e11be9976d 100644 --- a/packages/web_benchmarks/CHANGELOG.md +++ b/packages/web_benchmarks/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT +## 1.2.1 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Removes a few deprecated API usages. + +## 1.2.0 + +* Updates to web code to package `web: ^0.5.0`. +* Updates SDK version to Dart `^3.3.0`. Flutter `^3.19.0`. ## 1.1.1 diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart index baaab84fe504..d800e97a7589 100644 --- a/packages/web_benchmarks/lib/client.dart +++ b/packages/web_benchmarks/lib/client.dart @@ -7,7 +7,7 @@ import 'dart:convert' show json; import 'dart:js_interop'; import 'dart:math' as math; -import 'package:web/helpers.dart'; +import 'package:web/web.dart'; import 'src/common.dart'; import 'src/recorder.dart'; @@ -157,9 +157,7 @@ void _printResultsToScreen(Profile profile) { profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { body.appendHtml('

$scoreKey

'); body.appendHtml('
${timeseries.computeStats()}
'); - // TODO(kevmoo): remove `NodeGlue` cast when we no longer need to support - // pkg:web 0.3.0 - NodeGlue(body).append(TimeseriesVisualization(timeseries).render()); + body.appendChild(TimeseriesVisualization(timeseries).render()); }); } @@ -168,7 +166,7 @@ class TimeseriesVisualization { /// Creates a visualization for a [Timeseries]. TimeseriesVisualization(this._timeseries) { _stats = _timeseries.computeStats(); - _canvas = CanvasElement(); + _canvas = HTMLCanvasElement(); _screenWidth = window.screen.width; _canvas.width = _screenWidth; _canvas.height = (_kCanvasHeight * window.devicePixelRatio).round(); @@ -192,7 +190,7 @@ class TimeseriesVisualization { final Timeseries _timeseries; late TimeseriesStats _stats; - late CanvasElement _canvas; + late HTMLCanvasElement _canvas; late CanvasRenderingContext2D _ctx; late int _screenWidth; @@ -215,7 +213,7 @@ class TimeseriesVisualization { } /// Renders the timeseries into a `` and returns the canvas element. - CanvasElement render() { + HTMLCanvasElement render() { _ctx.translate(0, _kCanvasHeight * window.devicePixelRatio); _ctx.scale(1, -window.devicePixelRatio); @@ -320,7 +318,7 @@ class LocalBenchmarkServerClient { /// DevTools Protocol. Future startPerformanceTracing(String? benchmarkName) async { _checkNotManualMode(); - await HttpRequest.request( + await _requestXhr( '/start-performance-tracing?label=$benchmarkName', method: 'POST', mimeType: 'application/json', @@ -330,7 +328,7 @@ class LocalBenchmarkServerClient { /// Stops the performance tracing session started by [startPerformanceTracing]. Future stopPerformanceTracing() async { _checkNotManualMode(); - await HttpRequest.request( + await _requestXhr( '/stop-performance-tracing', method: 'POST', mimeType: 'application/json', @@ -358,7 +356,7 @@ class LocalBenchmarkServerClient { /// The server will halt the devicelab task and log the error. Future reportError(dynamic error, StackTrace stackTrace) async { _checkNotManualMode(); - await HttpRequest.request( + await _requestXhr( '/on-error', method: 'POST', mimeType: 'application/json', @@ -372,7 +370,7 @@ class LocalBenchmarkServerClient { /// Reports a message about the demo to the benchmark server. Future printToConsole(String report) async { _checkNotManualMode(); - await HttpRequest.request( + await _requestXhr( '/print-to-console', method: 'POST', mimeType: 'text/plain', @@ -386,7 +384,7 @@ class LocalBenchmarkServerClient { String url, { required String method, required String mimeType, - required String sendData, + String? sendData, }) { final Completer completer = Completer(); final XMLHttpRequest xhr = XMLHttpRequest(); @@ -396,7 +394,11 @@ class LocalBenchmarkServerClient { completer.complete(xhr); }); xhr.onError.listen(completer.completeError); - xhr.send(sendData.toJS); + if (sendData != null) { + xhr.send(sendData.toJS); + } else { + xhr.send(); + } return completer.future; } } diff --git a/packages/web_benchmarks/lib/src/recorder.dart b/packages/web_benchmarks/lib/src/recorder.dart index 49707b8c4b63..85d841f1f8cd 100644 --- a/packages/web_benchmarks/lib/src/recorder.dart +++ b/packages/web_benchmarks/lib/src/recorder.dart @@ -15,7 +15,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:web/helpers.dart' as html; +import 'package:web/web.dart' as html; import 'common.dart'; @@ -248,6 +248,7 @@ abstract class SceneBuilderRecorder extends Recorder { } }; PlatformDispatcher.instance.onDrawFrame = () { + final FlutterView? view = PlatformDispatcher.instance.implicitView; try { _profile.record('drawFrameDuration', () { final SceneBuilder sceneBuilder = SceneBuilder(); @@ -255,7 +256,9 @@ abstract class SceneBuilderRecorder extends Recorder { _profile.record('sceneBuildDuration', () { final Scene scene = sceneBuilder.build(); _profile.record('windowRenderDuration', () { - window.render(scene); + assert(view != null, + 'Cannot profile windowRenderDuration on a null View.'); + view!.render(scene); }, reported: false); }, reported: false); }, reported: true); diff --git a/packages/web_benchmarks/pubspec.yaml b/packages/web_benchmarks/pubspec.yaml index aa89736869cb..af66a69c9088 100644 --- a/packages/web_benchmarks/pubspec.yaml +++ b/packages/web_benchmarks/pubspec.yaml @@ -2,11 +2,11 @@ name: web_benchmarks description: A benchmark harness for performance-testing Flutter apps in Chrome. repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22 -version: 1.1.1 +version: 1.2.1 environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: collection: ^1.18.0 @@ -21,7 +21,7 @@ dependencies: shelf: ^1.2.0 shelf_static: ^1.1.0 test: ^1.19.5 - web: '>=0.3.0 <0.5.0' + web: ^0.5.0 webkit_inspection_protocol: ^1.0.0 topics: diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 403294ee39fe..44f60d72f469 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 3.16.0 +* Adds onReceivedHttpError WebViewClient callback to support + `PlatformNavigationDelegate.onHttpError`. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk to 34. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 0d0d37e5d3da..cfdadec40144 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -316,6 +316,58 @@ ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class WebResourceResponseData { + private @NonNull Long statusCode; + + public @NonNull Long getStatusCode() { + return statusCode; + } + + public void setStatusCode(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"statusCode\" is null."); + } + this.statusCode = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + WebResourceResponseData() {} + + public static final class Builder { + + private @Nullable Long statusCode; + + public @NonNull Builder setStatusCode(@NonNull Long setterArg) { + this.statusCode = setterArg; + return this; + } + + public @NonNull WebResourceResponseData build() { + WebResourceResponseData pigeonReturn = new WebResourceResponseData(); + pigeonReturn.setStatusCode(statusCode); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(statusCode); + return toListResult; + } + + static @NonNull WebResourceResponseData fromList(@NonNull ArrayList list) { + WebResourceResponseData pigeonResult = new WebResourceResponseData(); + Object statusCode = list.get(0); + pigeonResult.setStatusCode( + (statusCode == null) + ? null + : ((statusCode instanceof Integer) ? (Integer) statusCode : (Long) statusCode)); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class WebResourceErrorData { private @NonNull Long errorCode; @@ -2388,6 +2440,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return WebResourceErrorData.fromList((ArrayList) readValue(buffer)); case (byte) 129: return WebResourceRequestData.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return WebResourceResponseData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -2401,6 +2455,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof WebResourceRequestData) { stream.write(129); writeValue(stream, ((WebResourceRequestData) value).toList()); + } else if (value instanceof WebResourceResponseData) { + stream.write(130); + writeValue(stream, ((WebResourceResponseData) value).toList()); } else { super.writeValue(stream, value); } @@ -2455,6 +2512,23 @@ public void onPageFinished( channelReply -> callback.reply(null)); } + public void onReceivedHttpError( + @NonNull Long instanceIdArg, + @NonNull Long webViewInstanceIdArg, + @NonNull WebResourceRequestData requestArg, + @NonNull WebResourceResponseData responseArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError", + getCodec()); + channel.send( + new ArrayList( + Arrays.asList(instanceIdArg, webViewInstanceIdArg, requestArg, responseArg)), + channelReply -> callback.reply(null)); + } + public void onReceivedRequestError( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java index 7a5a057cf115..0bd280d705b2 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java @@ -9,6 +9,7 @@ import android.webkit.HttpAuthHandler; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; @@ -70,6 +71,16 @@ static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestDa return requestData.build(); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + static GeneratedAndroidWebView.WebResourceResponseData createWebResourceResponseData( + WebResourceResponse response) { + final GeneratedAndroidWebView.WebResourceResponseData.Builder responseData = + new GeneratedAndroidWebView.WebResourceResponseData.Builder() + .setStatusCode((long) response.getStatusCode()); + + return responseData.build(); + } + /** * Creates a Flutter api that sends messages to Dart. * @@ -110,6 +121,25 @@ public void onPageFinished( onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } + /** Passes arguments from {@link WebViewClient#onReceivedHttpError} to Dart. */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void onReceivedHttpError( + @NonNull WebViewClient webViewClient, + @NonNull WebView webView, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response, + @NonNull Reply callback) { + webViewFlutterApi.create(webView, reply -> {}); + + final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); + onReceivedHttpError( + getIdentifierForClient(webViewClient), + webViewIdentifier, + createWebResourceRequestData(request), + createWebResourceResponseData(response), + callback); + } + /** * Passes arguments from {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, * WebResourceError)} to Dart. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index 1ace7bfe0729..a2ed499629cb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -12,6 +12,7 @@ import android.webkit.HttpAuthHandler; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; @@ -55,6 +56,14 @@ public void onPageFinished(@NonNull WebView view, @NonNull String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } + @Override + public void onReceivedHttpError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response) { + flutterApi.onReceivedHttpError(this, view, request, response, reply -> {}); + } + @Override public void onReceivedError( @NonNull WebView view, @@ -140,6 +149,15 @@ public void onPageFinished(@NonNull WebView view, @NonNull String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onReceivedHttpError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response) { + flutterApi.onReceivedHttpError(this, view, request, response, reply -> {}); + } + // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is // enabled. The deprecated method is called when a device doesn't support this. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java index 8e6b58149d05..ea40742465f2 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -13,6 +13,7 @@ import android.net.Uri; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; @@ -136,4 +137,28 @@ public void doUpdateVisitedHistory() { .doUpdateVisitedHistory( eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), eq(true), any()); } + + @Test + public void onReceivedHttpError() { + final Uri mockUri = mock(Uri.class); + when(mockUri.toString()).thenReturn(""); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.getMethod()).thenReturn("method"); + when(mockRequest.getUrl()).thenReturn(mockUri); + when(mockRequest.isForMainFrame()).thenReturn(true); + when(mockRequest.getRequestHeaders()).thenReturn(null); + + final WebResourceResponse mockResponse = mock(WebResourceResponse.class); + when(mockResponse.getStatusCode()).thenReturn(404); + + webViewClient.onReceivedHttpError(mockWebView, mockRequest, mockResponse); + verify(mockFlutterApi) + .onReceivedHttpError( + eq(webViewClient), + eq(mockWebView), + any(WebResourceRequest.class), + any(WebResourceResponse.class), + any()); + } } diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index f599f07cc3a7..cee7d65f33fe 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -978,6 +978,81 @@ Future main() async { await pageFinishCompleter.future; }); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + })); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + unawaited(controller.loadRequest( + LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), + )); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + })); + unawaited(delegate.setOnPageFinished( + (_) => pageFinishCompleter.complete(), + )); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + unawaited(controller.loadHtmlString(testPage)); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index addccb00cde6..0f2972f23ef1 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -173,6 +173,11 @@ class _WebViewExampleState extends State { ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) + ..setOnHttpError((HttpResponseError error) { + debugPrint( + 'HTTP error occured on page: ${error.response?.statusCode}', + ); + }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart index 9f02d1b5f93f..78089d714b38 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -65,6 +65,11 @@ class AndroidWebViewProxy { final android_webview.WebViewClient Function({ void Function(android_webview.WebView webView, String url)? onPageStarted, void Function(android_webview.WebView webView, String url)? onPageFinished, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response, + )? onReceivedHttpError, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 0803198a176b..61174ccbf639 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -775,6 +775,7 @@ class WebViewClient extends JavaObject { WebViewClient({ this.onPageStarted, this.onPageFinished, + this.onReceivedHttpError, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, @@ -796,6 +797,7 @@ class WebViewClient extends JavaObject { WebViewClient.detached({ this.onPageStarted, this.onPageFinished, + this.onReceivedHttpError, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, @@ -908,6 +910,15 @@ class WebViewClient extends JavaObject { /// reflect the state of the DOM at this point. final void Function(WebView webView, String url)? onPageFinished; + /// Notify the host application that an HTTP error has been received from the + /// server while loading a resource. + /// + /// HTTP errors have status codes >= 400. This callback will be called for any + /// resource (iframe, image, etc.), not just for the main page. Thus, it is + /// recommended to perform minimum required work in this callback. + final void Function(WebView webView, WebResourceRequest request, + WebResourceResponse response)? onReceivedHttpError; + /// Report web resource loading error to the host application. /// /// These errors usually indicate inability to connect to the server. Note @@ -981,6 +992,7 @@ class WebViewClient extends JavaObject { return WebViewClient.detached( onPageStarted: onPageStarted, onPageFinished: onPageFinished, + onReceivedHttpError: onReceivedHttpError, onReceivedRequestError: onReceivedRequestError, onReceivedError: onReceivedError, requestLoading: requestLoading, @@ -1470,6 +1482,19 @@ class WebResourceRequest { final Map requestHeaders; } +/// Encapsulates information about the web resource response. +/// +/// See [WebViewClient.onReceivedHttpError]. +class WebResourceResponse { + /// Constructs a [WebResourceResponse]. + WebResourceResponse({ + required this.statusCode, + }); + + /// The HTTP status code associated with the response. + final int statusCode; +} + /// Encapsulates information about errors occurred during loading of web resources. /// /// See [WebViewClient.onReceivedRequestError]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart index e7ece16f722b..5d2707f9472f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart @@ -115,6 +115,27 @@ class WebResourceRequestData { } } +class WebResourceResponseData { + WebResourceResponseData({ + required this.statusCode, + }); + + int statusCode; + + Object encode() { + return [ + statusCode, + ]; + } + + static WebResourceResponseData decode(Object result) { + result as List; + return WebResourceResponseData( + statusCode: result[0]! as int, + ); + } +} + class WebResourceErrorData { WebResourceErrorData({ required this.errorCode, @@ -1700,6 +1721,9 @@ class _WebViewClientFlutterApiCodec extends StandardMessageCodec { } else if (value is WebResourceRequestData) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is WebResourceResponseData) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1712,6 +1736,8 @@ class _WebViewClientFlutterApiCodec extends StandardMessageCodec { return WebResourceErrorData.decode(readValue(buffer)!); case 129: return WebResourceRequestData.decode(readValue(buffer)!); + case 130: + return WebResourceResponseData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -1725,6 +1751,9 @@ abstract class WebViewClientFlutterApi { void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedHttpError(int instanceId, int webViewInstanceId, + WebResourceRequestData request, WebResourceResponseData response); + void onReceivedRequestError(int instanceId, int webViewInstanceId, WebResourceRequestData request, WebResourceErrorData error); @@ -1796,6 +1825,38 @@ abstract class WebViewClientFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null int.'); + final int? arg_webViewInstanceId = (args[1] as int?); + assert(arg_webViewInstanceId != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null int.'); + final WebResourceRequestData? arg_request = + (args[2] as WebResourceRequestData?); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null WebResourceRequestData.'); + final WebResourceResponseData? arg_response = + (args[3] as WebResourceResponseData?); + assert(arg_response != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null WebResourceResponseData.'); + api.onReceivedHttpError(arg_instanceId!, arg_webViewInstanceId!, + arg_request!, arg_response!); + return; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.webview_flutter_android.WebViewClientFlutterApi.onReceivedRequestError', diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index f157c01a7424..c6f3c29c1b83 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -25,6 +25,13 @@ WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) { ); } +/// Converts [WebResourceResponseData] to [WebResourceResponse] +WebResourceResponse _toWebResourceResponse(WebResourceResponseData data) { + return WebResourceResponse( + statusCode: data.statusCode, + ); +} + /// Converts [WebResourceErrorData] to [WebResourceError]. WebResourceError _toWebResourceError(WebResourceErrorData data) { return WebResourceError( @@ -700,6 +707,34 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { } } + @override + void onReceivedHttpError( + int instanceId, + int webViewInstanceId, + WebResourceRequestData request, + WebResourceResponseData response, + ) { + final WebViewClient? instance = instanceManager + .getInstanceWithWeakReference(instanceId) as WebViewClient?; + final WebView? webViewInstance = instanceManager + .getInstanceWithWeakReference(webViewInstanceId) as WebView?; + assert( + instance != null, + 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', + ); + assert( + webViewInstance != null, + 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', + ); + if (instance!.onReceivedHttpError != null) { + instance.onReceivedHttpError!( + webViewInstance!, + _toWebResourceRequest(request), + _toWebResourceResponse(response), + ); + } + } + @override void onReceivedError( int instanceId, diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index b9dfe24a7603..f353532bc5dd 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -1297,6 +1297,25 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { callback(url); } }, + onReceivedHttpError: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response, + ) { + if (weakThis.target?._onHttpError != null) { + weakThis.target!._onHttpError!( + HttpResponseError( + request: WebResourceRequest( + uri: Uri.parse(request.url), + ), + response: WebResourceResponse( + uri: null, + statusCode: response.statusCode, + ), + ), + ); + } + }, onReceivedRequestError: ( android_webview.WebView webView, android_webview.WebResourceRequest request, @@ -1429,6 +1448,7 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { PageEventCallback? _onPageFinished; PageEventCallback? _onPageStarted; + HttpResponseErrorCallback? _onHttpError; ProgressCallback? _onProgress; WebResourceErrorCallback? _onWebResourceError; NavigationRequestCallback? _onNavigationRequest; @@ -1503,6 +1523,13 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { _onPageFinished = onPageFinished; } + @override + Future setOnHttpError( + HttpResponseErrorCallback onHttpError, + ) async { + _onHttpError = onHttpError; + } + @override Future setOnProgress( ProgressCallback onProgress, diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 8f40d9120ce7..73b8093d05eb 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -111,6 +111,14 @@ class WebResourceRequestData { Map requestHeaders; } +class WebResourceResponseData { + WebResourceResponseData( + this.statusCode, + ); + + int statusCode; +} + class WebResourceErrorData { WebResourceErrorData(this.errorCode, this.description); @@ -337,6 +345,13 @@ abstract class WebViewClientFlutterApi { void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedHttpError( + int instanceId, + int webViewInstanceId, + WebResourceRequestData request, + WebResourceResponseData response, + ); + void onReceivedRequestError( int instanceId, int webViewInstanceId, diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 9f3fd0602a1f..01add9158426 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.15.0 +version: 3.16.0 environment: sdk: ^3.1.0 @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^2.9.0 + webview_flutter_platform_interface: ^2.10.0 dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 36681d092164..85b99dec2327 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -59,6 +59,29 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); + test('onHttpError from onReceivedHttpError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final HttpResponseError callbackError; + androidNavigationDelegate.setOnHttpError( + (HttpResponseError httpError) => callbackError = httpError); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedHttpError!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + android_webview.WebResourceResponse(statusCode: 401)); + + expect(callbackError.response?.statusCode, 401); + }); + test('onWebResourceError from onReceivedRequestError', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); @@ -532,6 +555,7 @@ class CapturingWebViewClient extends android_webview.WebViewClient { CapturingWebViewClient({ super.onPageFinished, super.onPageStarted, + super.onReceivedHttpError, super.onReceivedError, super.onReceivedHttpAuthRequest, super.onReceivedRequestError, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index f912e8d5e64a..dcc5b27abcd8 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -140,6 +140,11 @@ void main() { onPageFinished, void Function(android_webview.WebView webView, String url)? onPageStarted, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response)? + onReceivedHttpError, @Deprecated('Only called on Android version < 23.') void Function( android_webview.WebView webView, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index a7abc51261db..73c513c8af33 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -343,6 +343,17 @@ class MockAndroidNavigationDelegate extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override + _i9.Future setOnHttpError(_i3.HttpResponseErrorCallback? onHttpError) => + (super.noSuchMethod( + Invocation.method( + #setOnHttpError, + [onHttpError], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override _i9.Future setOnProgress(_i3.ProgressCallback? onProgress) => (super.noSuchMethod( @@ -388,17 +399,6 @@ class MockAndroidNavigationDelegate extends _i1.Mock returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); - - @override - _i9.Future setOnHttpError(_i3.HttpResponseErrorCallback? onHttpError) => - (super.noSuchMethod( - Invocation.method( - #setOnHttpError, - [onHttpError], - ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); } /// A class which mocks [AndroidWebViewController]. @@ -892,7 +892,7 @@ class MockAndroidWebViewProxy extends _i1.Mock implements _i10.AndroidWebViewProxy { @override _i2.WebView Function( - {dynamic Function( + {void Function( int, int, int, @@ -900,7 +900,7 @@ class MockAndroidWebViewProxy extends _i1.Mock )? onScrollChanged}) get createAndroidWebView => (super.noSuchMethod( Invocation.getter(#createAndroidWebView), returnValue: ( - {dynamic Function( + {void Function( int, int, int, @@ -911,7 +911,7 @@ class MockAndroidWebViewProxy extends _i1.Mock Invocation.getter(#createAndroidWebView), ), returnValueForMissingStub: ( - {dynamic Function( + {void Function( int, int, int, @@ -922,7 +922,7 @@ class MockAndroidWebViewProxy extends _i1.Mock Invocation.getter(#createAndroidWebView), ), ) as _i2.WebView Function( - {dynamic Function( + {void Function( int, int, int, @@ -1137,6 +1137,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedHttpAuthRequest, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -1178,6 +1183,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedHttpAuthRequest, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -1222,6 +1232,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedHttpAuthRequest, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -1266,6 +1281,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedHttpAuthRequest, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -2117,6 +2137,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override _i9.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => (super.noSuchMethod( @@ -2127,6 +2148,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override _i9.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => (super.noSuchMethod( @@ -2137,6 +2159,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index d542c498772c..36b9c0343578 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -642,6 +642,37 @@ void main() { expect(result, [mockWebView, 'https://www.google.com']); }); + test('onReceivedHttpError', () { + late final List result; + when(mockWebViewClient.onReceivedHttpError).thenReturn( + ( + WebView webView, + WebResourceRequest request, + WebResourceResponse response, + ) { + result = [webView, request, response]; + }, + ); + + flutterApi.onReceivedHttpError( + mockWebViewClientInstanceId, + mockWebViewInstanceId, + WebResourceRequestData( + url: 'https://www.google.com', + isForMainFrame: true, + hasGesture: true, + method: 'GET', + isRedirect: false, + requestHeaders: {}, + ), + WebResourceResponseData( + statusCode: 401, + ), + ); + + expect(result, [mockWebView, isNotNull, isNotNull]); + }); + test('onReceivedRequestError', () { late final List result; when(mockWebViewClient.onReceivedRequestError).thenReturn( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 4a9ced6a07b8..54002135ab51 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -510,6 +510,7 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override void setSynchronousReturnValueForOnJsAlert( int? instanceId, @@ -525,6 +526,7 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override void setSynchronousReturnValueForOnJsConfirm( int? instanceId, @@ -540,6 +542,7 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override void setSynchronousReturnValueForOnJsPrompt( int? instanceId, @@ -1340,6 +1343,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i5.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => (super.noSuchMethod( @@ -1350,6 +1354,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i5.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => (super.noSuchMethod( @@ -1360,6 +1365,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart index f7a11fb9e872..84a778786ecc 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -880,6 +880,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i5.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => (super.noSuchMethod( @@ -890,6 +891,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i5.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => (super.noSuchMethod( @@ -900,6 +902,7 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 8aa73ace0264..70ba80cee6ad 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.13.0 + +* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the + `PlatformNavigationDelegate.onHttpError` callback. + ## 3.12.0 * Adds support for `setOnScrollPositionChange` method to the `WebKitWebViewController`. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 91d9075cd471..80445b96fee0 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -987,6 +987,81 @@ Future main() async { }, ); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + })); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + unawaited(controller.loadRequest( + LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), + )); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + })); + unawaited(delegate.setOnPageFinished( + (_) => pageFinishCompleter.complete(), + )); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + unawaited(controller.loadHtmlString(testPage)); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m index 7613bf02f75c..a4ff58a47ea5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m @@ -171,4 +171,34 @@ - (void)testNSKeyValueChangeKeyConversionReturnsUnknownIfUnrecognized { - (void)testWKNavigationTypeConversionReturnsUnknownIfUnrecognized { XCTAssertEqual(FWFWKNavigationTypeFromNativeWKNavigationType(-15), FWFWKNavigationTypeUnknown); } + +- (void)testFWFWKNavigationResponseDataFromNativeNavigationResponse { + WKNavigationResponse *mockResponse = OCMClassMock([WKNavigationResponse class]); + OCMStub([mockResponse isForMainFrame]).andReturn(YES); + + NSHTTPURLResponse *mockURLResponse = OCMClassMock([NSHTTPURLResponse class]); + OCMStub([mockURLResponse statusCode]).andReturn(1); + OCMStub([mockResponse response]).andReturn(mockURLResponse); + + FWFWKNavigationResponseData *data = + FWFWKNavigationResponseDataFromNativeNavigationResponse(mockResponse); + XCTAssertEqual(data.forMainFrame, YES); +} + +- (void)testFWFNSHttpUrlResponseDataFromNativeNSURLResponse { + NSHTTPURLResponse *mockResponse = OCMClassMock([NSHTTPURLResponse class]); + OCMStub([mockResponse statusCode]).andReturn(1); + + FWFNSHttpUrlResponseData *data = FWFNSHttpUrlResponseDataFromNativeNSURLResponse(mockResponse); + XCTAssertEqual(data.statusCode, 1); +} + +- (void)testFWFNativeWKNavigationResponsePolicyFromEnum { + XCTAssertEqual( + FWFNativeWKNavigationResponsePolicyFromEnum(FWFWKNavigationResponsePolicyEnumAllow), + WKNavigationResponsePolicyAllow); + XCTAssertEqual( + FWFNativeWKNavigationResponsePolicyFromEnum(FWFWKNavigationResponsePolicyEnumCancel), + WKNavigationResponsePolicyCancel); +} @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m index dc4cb3cf8abe..829d27643bfb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m @@ -267,4 +267,46 @@ - (void)testDidReceiveAuthenticationChallenge { XCTAssertEqual(callbackDisposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge); XCTAssertEqualObjects(callbackCredential, credential); } + +- (void)testDecidePolicyForNavigationResponse { + FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; + + FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager + identifier:0]; + FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = + [self mockFlutterApiWithManager:instanceManager]; + + OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); + + WKWebView *mockWebView = OCMClassMock([WKWebView class]); + [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; + + WKNavigationResponse *mockNavigationResponse = OCMClassMock([WKNavigationResponse class]); + OCMStub([mockNavigationResponse isForMainFrame]).andReturn(YES); + + NSHTTPURLResponse *mockURLResponse = OCMClassMock([NSHTTPURLResponse class]); + OCMStub([mockURLResponse statusCode]).andReturn(1); + OCMStub([mockNavigationResponse response]).andReturn(mockURLResponse); + + OCMStub([mockFlutterAPI + decidePolicyForNavigationResponseForDelegateWithIdentifier:0 + webViewIdentifier:1 + navigationResponse:OCMOCK_ANY + completion: + ([OCMArg + invokeBlockWithArgs: + [[FWFWKNavigationResponsePolicyEnumBox + alloc] + initWithValue: + FWFWKNavigationResponsePolicyEnumAllow], + [NSNull null], nil])]); + + WKNavigationResponsePolicy __block callbackPolicy = -1; + [mockDelegate webView:mockWebView + decidePolicyForNavigationResponse:mockNavigationResponse + decisionHandler:^(WKNavigationResponsePolicy policy) { + callbackPolicy = policy; + }]; + XCTAssertEqual(callbackPolicy, WKNavigationResponsePolicyAllow); +} @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index 36f3a1eb9ad4..68a9f1a0c4d8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -174,6 +174,9 @@ class _WebViewExampleState extends State { ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) + ..setOnHttpError((HttpResponseError error) { + debugPrint('Error occurred on page: ${error.response?.statusCode}'); + }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h index e9405f3bb90d..f97de9c8c19e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h @@ -8,101 +8,100 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Converts an FWFNSUrlRequestData to an NSURLRequest. - * - * @param data The data object containing information to create an NSURLRequest. - * - * @return An NSURLRequest or nil if data could not be converted. - */ +/// Converts an FWFNSUrlRequestData to an NSURLRequest. +/// +/// @param data The data object containing information to create an NSURLRequest. +/// +/// @return An NSURLRequest or nil if data could not be converted. extern NSURLRequest *_Nullable FWFNativeNSURLRequestFromRequestData(FWFNSUrlRequestData *data); -/** - * Converts an FWFNSHttpCookieData to an NSHTTPCookie. - * - * @param data The data object containing information to create an NSHTTPCookie. - * - * @return An NSHTTPCookie or nil if data could not be converted. - */ +/// Converts an FWFNSHttpCookieData to an NSHTTPCookie. +/// +/// @param data The data object containing information to create an NSHTTPCookie. +/// +/// @return An NSHTTPCookie or nil if data could not be converted. extern NSHTTPCookie *_Nullable FWFNativeNSHTTPCookieFromCookieData(FWFNSHttpCookieData *data); -/** - * Converts an FWFNSKeyValueObservingOptionsEnumData to an NSKeyValueObservingOptions. - * - * @param data The data object containing information to create an NSKeyValueObservingOptions. - * - * @return An NSKeyValueObservingOptions or -1 if data could not be converted. - */ +/// Converts an FWFNSKeyValueObservingOptionsEnumData to an NSKeyValueObservingOptions. +/// +/// @param data The data object containing information to create an NSKeyValueObservingOptions. +/// +/// @return An NSKeyValueObservingOptions or -1 if data could not be converted. extern NSKeyValueObservingOptions FWFNativeNSKeyValueObservingOptionsFromEnumData( FWFNSKeyValueObservingOptionsEnumData *data); -/** - * Converts an FWFNSHTTPCookiePropertyKeyEnumData to an NSHTTPCookiePropertyKey. - * - * @param data The data object containing information to create an NSHTTPCookiePropertyKey. - * - * @return An NSHttpCookiePropertyKey or nil if data could not be converted. - */ +/// Converts an FWFNSHTTPCookiePropertyKeyEnumData to an NSHTTPCookiePropertyKey. +/// +/// @param data The data object containing information to create an NSHTTPCookiePropertyKey. +/// +/// @return An NSHttpCookiePropertyKey or nil if data could not be converted. extern NSHTTPCookiePropertyKey _Nullable FWFNativeNSHTTPCookiePropertyKeyFromEnumData( FWFNSHttpCookiePropertyKeyEnumData *data); -/** - * Converts a WKUserScriptData to a WKUserScript. - * - * @param data The data object containing information to create a WKUserScript. - * - * @return A WKUserScript or nil if data could not be converted. - */ +/// Converts a WKUserScriptData to a WKUserScript. +/// +/// @param data The data object containing information to create a WKUserScript. +/// +/// @return A WKUserScript or nil if data could not be converted. extern WKUserScript *FWFNativeWKUserScriptFromScriptData(FWFWKUserScriptData *data); -/** - * Converts an FWFWKUserScriptInjectionTimeEnumData to a WKUserScriptInjectionTime. - * - * @param data The data object containing information to create a WKUserScriptInjectionTime. - * - * @return A WKUserScriptInjectionTime or -1 if data could not be converted. - */ +/// Converts an FWFWKUserScriptInjectionTimeEnumData to a WKUserScriptInjectionTime. +/// +/// @param data The data object containing information to create a WKUserScriptInjectionTime. +/// +/// @return A WKUserScriptInjectionTime or -1 if data could not be converted. extern WKUserScriptInjectionTime FWFNativeWKUserScriptInjectionTimeFromEnumData( FWFWKUserScriptInjectionTimeEnumData *data); -/** - * Converts an FWFWKAudiovisualMediaTypeEnumData to a WKAudiovisualMediaTypes. - * - * @param data The data object containing information to create a WKAudiovisualMediaTypes. - * - * @return A WKAudiovisualMediaType or -1 if data could not be converted. - */ +/// Converts an FWFWKAudiovisualMediaTypeEnumData to a WKAudiovisualMediaTypes. +/// +/// @param data The data object containing information to create a WKAudiovisualMediaTypes. +/// +/// @return A WKAudiovisualMediaType or -1 if data could not be converted. extern WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData( FWFWKAudiovisualMediaTypeEnumData *data); -/** - * Converts an FWFWKWebsiteDataTypeEnumData to a WKWebsiteDataType. - * - * @param data The data object containing information to create a WKWebsiteDataType. - * - * @return A WKWebsiteDataType or nil if data could not be converted. - */ +/// Converts an FWFWKWebsiteDataTypeEnumData to a WKWebsiteDataType. +/// +/// @param data The data object containing information to create a WKWebsiteDataType. +/// +/// @return A WKWebsiteDataType or nil if data could not be converted. extern NSString *_Nullable FWFNativeWKWebsiteDataTypeFromEnumData( FWFWKWebsiteDataTypeEnumData *data); +/// Converts a WKNavigationAction to an FWFWKNavigationActionData. +/// +/// @param action The object containing information to create a WKNavigationActionData. +/// +/// @return A FWFWKNavigationActionData. +extern FWFWKNavigationActionData *FWFWKNavigationActionDataFromNativeWKNavigationAction( + WKNavigationAction *action); + +/// Converts a NSURLRequest to an FWFNSUrlRequestData. +/// +/// @param request The object containing information to create a WKNavigationActionData. +/// +/// @return A FWFNSUrlRequestData. +extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request); + /** - * Converts a WKNavigationAction to an FWFWKNavigationActionData. + * Converts a WKNavigationResponse to an FWFWKNavigationResponseData. * - * @param action The object containing information to create a WKNavigationActionData. + * @param response The object containing information to create a WKNavigationResponseData. * - * @return A FWFWKNavigationActionData. + * @return A FWFWKNavigationResponseData. */ -extern FWFWKNavigationActionData *FWFWKNavigationActionDataFromNativeWKNavigationAction( - WKNavigationAction *action); - +extern FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse( + WKNavigationResponse *response); /** - * Converts a NSURLRequest to an FWFNSUrlRequestData. + * Converts a NSURLResponse to an FWFNSHttpUrlResponseData. * - * @param request The object containing information to create a WKNavigationActionData. + * @param response The object containing information to create a WKNavigationActionData. * - * @return A FWFNSUrlRequestData. + * @return A FWFNSHttpUrlResponseData. */ -extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request); +extern FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse( + NSURLResponse *response); /** * Converts a WKFrameInfo to an FWFWKFrameInfoData. @@ -113,15 +112,23 @@ extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLReque */ extern FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info); +/// Converts an FWFWKNavigationActionPolicyEnumData to a WKNavigationActionPolicy. +/// +/// @param data The data object containing information to create a WKNavigationActionPolicy. +/// +/// @return A WKNavigationActionPolicy or -1 if data could not be converted. +extern WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( + FWFWKNavigationActionPolicyEnumData *data); + /** - * Converts an FWFWKNavigationActionPolicyEnumData to a WKNavigationActionPolicy. + * Converts an FWFWKNavigationResponsePolicyEnumData to a WKNavigationResponsePolicy. * - * @param data The data object containing information to create a WKNavigationActionPolicy. + * @param policy The data object containing information to create a WKNavigationResponsePolicy. * - * @return A WKNavigationActionPolicy or -1 if data could not be converted. + * @return A WKNavigationResponsePolicy or -1 if data could not be converted. */ -extern WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( - FWFWKNavigationActionPolicyEnumData *data); +extern WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnum( + FWFWKNavigationResponsePolicyEnum policy); /** * Converts a NSError to an FWFNSErrorData. @@ -132,85 +139,70 @@ extern WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( */ extern FWFNSErrorData *FWFNSErrorDataFromNativeNSError(NSError *error); -/** - * Converts an NSKeyValueChangeKey to a FWFNSKeyValueChangeKeyEnumData. - * - * @param key The data object containing information to create a FWFNSKeyValueChangeKeyEnumData. - * - * @return A FWFNSKeyValueChangeKeyEnumData. - */ +/// Converts an NSKeyValueChangeKey to a FWFNSKeyValueChangeKeyEnumData. +/// +/// @param key The data object containing information to create a FWFNSKeyValueChangeKeyEnumData. +/// +/// @return A FWFNSKeyValueChangeKeyEnumData. extern FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey( NSKeyValueChangeKey key); -/** - * Converts a WKScriptMessage to an FWFWKScriptMessageData. - * - * @param message The object containing information to create a FWFWKScriptMessageData. - * - * @return A FWFWKScriptMessageData. - */ +/// Converts a WKScriptMessage to an FWFWKScriptMessageData. +/// +/// @param message The object containing information to create a FWFWKScriptMessageData. +/// +/// @return A FWFWKScriptMessageData. extern FWFWKScriptMessageData *FWFWKScriptMessageDataFromNativeWKScriptMessage( WKScriptMessage *message); -/** - * Converts a WKNavigationType to an FWFWKNavigationType. - * - * @param type The object containing information to create a FWFWKNavigationType - * - * @return A FWFWKNavigationType. - */ +/// Converts a WKNavigationType to an FWFWKNavigationType. +/// +/// @param type The object containing information to create a FWFWKNavigationType +/// +/// @return A FWFWKNavigationType. extern FWFWKNavigationType FWFWKNavigationTypeFromNativeWKNavigationType(WKNavigationType type); -/** - * Converts a WKSecurityOrigin to an FWFWKSecurityOriginData. - * - * @param origin The object containing information to create an FWFWKSecurityOriginData. - * - * @return An FWFWKSecurityOriginData. - */ +/// Converts a WKSecurityOrigin to an FWFWKSecurityOriginData. +/// +/// @param origin The object containing information to create an FWFWKSecurityOriginData. +/// +/// @return An FWFWKSecurityOriginData. extern FWFWKSecurityOriginData *FWFWKSecurityOriginDataFromNativeWKSecurityOrigin( WKSecurityOrigin *origin); -/** - * Converts an FWFWKPermissionDecisionData to a WKPermissionDecision. - * - * @param data The data object containing information to create a WKPermissionDecision. - * - * @return A WKPermissionDecision or -1 if data could not be converted. - */ +/// Converts an FWFWKPermissionDecisionData to a WKPermissionDecision. +/// +/// @param data The data object containing information to create a WKPermissionDecision. +/// +/// @return A WKPermissionDecision or -1 if data could not be converted. API_AVAILABLE(ios(15.0)) extern WKPermissionDecision FWFNativeWKPermissionDecisionFromData( FWFWKPermissionDecisionData *data); -/** - * Converts an WKMediaCaptureType to a FWFWKMediaCaptureTypeData. - * - * @param type The data object containing information to create a FWFWKMediaCaptureTypeData. - * - * @return A FWFWKMediaCaptureTypeData or nil if data could not be converted. - */ +/// Converts an WKMediaCaptureType to a FWFWKMediaCaptureTypeData. +/// +/// @param type The data object containing information to create a FWFWKMediaCaptureTypeData. +/// +/// @return A FWFWKMediaCaptureTypeData or nil if data could not be converted. API_AVAILABLE(ios(15.0)) extern FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType( WKMediaCaptureType type); -/** - * Converts an FWFNSUrlSessionAuthChallengeDisposition to an NSURLSessionAuthChallengeDisposition. - * - * @param value The object containing information to create an NSURLSessionAuthChallengeDisposition. - * - * @return A NSURLSessionAuthChallengeDisposition or -1 if data could not be converted. - */ +/// Converts an FWFNSUrlSessionAuthChallengeDisposition to an NSURLSessionAuthChallengeDisposition. +/// +/// @param value The object containing information to create an +/// NSURLSessionAuthChallengeDisposition. +/// +/// @return A NSURLSessionAuthChallengeDisposition or -1 if data could not be converted. extern NSURLSessionAuthChallengeDisposition FWFNativeNSURLSessionAuthChallengeDispositionFromFWFNSUrlSessionAuthChallengeDisposition( FWFNSUrlSessionAuthChallengeDisposition value); -/** - * Converts an FWFNSUrlCredentialPersistence to an NSURLCredentialPersistence. - * - * @param value The object containing information to create an NSURLCredentialPersistence. - * - * @return A NSURLCredentialPersistence or -1 if data could not be converted. - */ +/// Converts an FWFNSUrlCredentialPersistence to an NSURLCredentialPersistence. +/// +/// @param value The object containing information to create an NSURLCredentialPersistence. +/// +/// @return A NSURLCredentialPersistence or -1 if data could not be converted. extern NSURLCredentialPersistence FWFNativeNSURLCredentialPersistenceFromFWFNSUrlCredentialPersistence( FWFNSUrlCredentialPersistence value); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m index 9a5cc86fe951..51a5ada5030b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m @@ -181,6 +181,24 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData( request:FWFNSUrlRequestDataFromNativeNSURLRequest(info.request)]; } +FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse( + WKNavigationResponse *response) { + return [FWFWKNavigationResponseData + makeWithResponse:FWFNSHttpUrlResponseDataFromNativeNSURLResponse(response.response) + forMainFrame:response.forMainFrame]; +} + +/// Cast the NSURLResponse object to NSHTTPURLResponse. +/// +/// NSURLResponse doesn't contain the status code so it must be cast to NSHTTPURLResponse. +/// This cast will always succeed because the NSURLResponse object actually is an instance of +/// NSHTTPURLResponse. See: +/// https://developer.apple.com/documentation/foundation/nsurlresponse#overview +FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse(NSURLResponse *response) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + return [FWFNSHttpUrlResponseData makeWithStatusCode:httpResponse.statusCode]; +} + WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( FWFWKNavigationActionPolicyEnumData *data) { switch (data.value) { @@ -209,6 +227,18 @@ WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( return [FWFNSErrorData makeWithCode:error.code domain:error.domain userInfo:userInfo]; } +WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnum( + FWFWKNavigationResponsePolicyEnum policy) { + switch (policy) { + case FWFWKNavigationResponsePolicyEnumAllow: + return WKNavigationResponsePolicyAllow; + case FWFWKNavigationResponsePolicyEnumCancel: + return WKNavigationResponsePolicyCancel; + } + + return -1; +} + FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey( NSKeyValueChangeKey key) { if ([key isEqualToString:NSKeyValueChangeIndexesKey]) { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h index 3a0fa3021d2f..984cfc93cc33 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.1.2), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -130,6 +130,20 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationActionPolicyEnum) { - (instancetype)initWithValue:(FWFWKNavigationActionPolicyEnum)value; @end +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +typedef NS_ENUM(NSUInteger, FWFWKNavigationResponsePolicyEnum) { + FWFWKNavigationResponsePolicyEnumAllow = 0, + FWFWKNavigationResponsePolicyEnumCancel = 1, +}; + +/// Wrapper for FWFWKNavigationResponsePolicyEnum to allow for nullability. +@interface FWFWKNavigationResponsePolicyEnumBox : NSObject +@property(nonatomic, assign) FWFWKNavigationResponsePolicyEnum value; +- (instancetype)initWithValue:(FWFWKNavigationResponsePolicyEnum)value; +@end + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -341,8 +355,10 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) { @class FWFWKPermissionDecisionData; @class FWFWKMediaCaptureTypeData; @class FWFNSUrlRequestData; +@class FWFNSHttpUrlResponseData; @class FWFWKUserScriptData; @class FWFWKNavigationActionData; +@class FWFWKNavigationResponseData; @class FWFWKFrameInfoData; @class FWFNSErrorData; @class FWFWKScriptMessageData; @@ -430,6 +446,16 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) { @property(nonatomic, copy) NSDictionary *allHttpHeaderFields; @end +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +@interface FWFNSHttpUrlResponseData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithStatusCode:(NSInteger)statusCode; +@property(nonatomic, assign) NSInteger statusCode; +@end + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -458,6 +484,18 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) { @property(nonatomic, assign) FWFWKNavigationType navigationType; @end +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +@interface FWFWKNavigationResponseData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithResponse:(FWFNSHttpUrlResponseData *)response + forMainFrame:(BOOL)forMainFrame; +@property(nonatomic, strong) FWFNSHttpUrlResponseData *response; +@property(nonatomic, assign) BOOL forMainFrame; +@end + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -778,6 +816,15 @@ NSObject *FWFWKNavigationDelegateFlutterApiGetCodec(void); (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, FlutterError *_Nullable))completion; +- (void)decidePolicyForNavigationResponseForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier + navigationResponse:(FWFWKNavigationResponseData *) + navigationResponse + completion: + (void (^)( + FWFWKNavigationResponsePolicyEnumBox + *_Nullable, + FlutterError *_Nullable))completion; - (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)identifier webViewIdentifier:(NSInteger)webViewIdentifier error:(FWFNSErrorData *)error diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m index 450efd648313..352f9b1c161d 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.1.2), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "FWFGeneratedWebKitApis.h" @@ -16,29 +16,6 @@ #error File requires ARC to be enabled. #endif -static NSArray *wrapResult(id result, FlutterError *error) { - if (error) { - return @[ - error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] - ]; - } - return @[ result ?: [NSNull null] ]; -} - -static FlutterError *createConnectionError(NSString *channelName) { - return [FlutterError - errorWithCode:@"channel-error" - message:[NSString stringWithFormat:@"%@/%@/%@", - @"Unable to establish connection on channel: '", - channelName, @"'."] - details:@""]; -} - -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { - id result = array[key]; - return (result == [NSNull null]) ? nil : result; -} - /// Mirror of NSKeyValueObservingOptions. /// /// See @@ -133,6 +110,19 @@ - (instancetype)initWithValue:(FWFWKNavigationActionPolicyEnum)value { } @end +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +@implementation FWFWKNavigationResponsePolicyEnumBox +- (instancetype)initWithValue:(FWFWKNavigationResponsePolicyEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -212,6 +202,19 @@ - (instancetype)initWithValue:(FWFNSUrlCredentialPersistence)value { } @end +static NSArray *wrapResult(id result, FlutterError *error) { + if (error) { + return @[ + error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] + ]; + } + return @[ result ?: [NSNull null] ]; +} +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { + id result = array[key]; + return (result == [NSNull null]) ? nil : result; +} + @interface FWFNSKeyValueObservingOptionsEnumData () + (FWFNSKeyValueObservingOptionsEnumData *)fromList:(NSArray *)list; + (nullable FWFNSKeyValueObservingOptionsEnumData *)nullableFromList:(NSArray *)list; @@ -272,6 +275,12 @@ + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface FWFNSHttpUrlResponseData () ++ (FWFNSHttpUrlResponseData *)fromList:(NSArray *)list; ++ (nullable FWFNSHttpUrlResponseData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FWFWKUserScriptData () + (FWFWKUserScriptData *)fromList:(NSArray *)list; + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list; @@ -284,6 +293,12 @@ + (nullable FWFWKNavigationActionData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface FWFWKNavigationResponseData () ++ (FWFWKNavigationResponseData *)fromList:(NSArray *)list; ++ (nullable FWFWKNavigationResponseData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FWFWKFrameInfoData () + (FWFWKFrameInfoData *)fromList:(NSArray *)list; + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list; @@ -558,6 +573,27 @@ - (NSArray *)toList { } @end +@implementation FWFNSHttpUrlResponseData ++ (instancetype)makeWithStatusCode:(NSInteger)statusCode { + FWFNSHttpUrlResponseData *pigeonResult = [[FWFNSHttpUrlResponseData alloc] init]; + pigeonResult.statusCode = statusCode; + return pigeonResult; +} ++ (FWFNSHttpUrlResponseData *)fromList:(NSArray *)list { + FWFNSHttpUrlResponseData *pigeonResult = [[FWFNSHttpUrlResponseData alloc] init]; + pigeonResult.statusCode = [GetNullableObjectAtIndex(list, 0) integerValue]; + return pigeonResult; +} ++ (nullable FWFNSHttpUrlResponseData *)nullableFromList:(NSArray *)list { + return (list) ? [FWFNSHttpUrlResponseData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.statusCode), + ]; +} +@end + @implementation FWFWKUserScriptData + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime @@ -618,6 +654,32 @@ - (NSArray *)toList { } @end +@implementation FWFWKNavigationResponseData ++ (instancetype)makeWithResponse:(FWFNSHttpUrlResponseData *)response + forMainFrame:(BOOL)forMainFrame { + FWFWKNavigationResponseData *pigeonResult = [[FWFWKNavigationResponseData alloc] init]; + pigeonResult.response = response; + pigeonResult.forMainFrame = forMainFrame; + return pigeonResult; +} ++ (FWFWKNavigationResponseData *)fromList:(NSArray *)list { + FWFWKNavigationResponseData *pigeonResult = [[FWFWKNavigationResponseData alloc] init]; + pigeonResult.response = + [FWFNSHttpUrlResponseData nullableFromList:(GetNullableObjectAtIndex(list, 0))]; + pigeonResult.forMainFrame = [GetNullableObjectAtIndex(list, 1) boolValue]; + return pigeonResult; +} ++ (nullable FWFWKNavigationResponseData *)nullableFromList:(NSArray *)list { + return (list) ? [FWFWKNavigationResponseData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.response ? [self.response toList] : [NSNull null]), + @(self.forMainFrame), + ]; +} +@end + @implementation FWFWKFrameInfoData + (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; @@ -1323,26 +1385,28 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } - (void)createWithIdentifier:(NSInteger)arg_identifier completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationFlutterApi.create"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName: + @"dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFWKWebViewConfigurationFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -1716,28 +1780,30 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina userContentControllerIdentifier:(NSInteger)arg_userContentControllerIdentifier message:(FWFWKScriptMessageData *)arg_message completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage" binaryMessenger:self.binaryMessenger codec:FWFWKScriptMessageHandlerFlutterApiGetCodec()]; - [channel sendMessage:@[ - @(arg_identifier), @(arg_userContentControllerIdentifier), arg_message ?: [NSNull null] - ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ + @(arg_identifier), @(arg_userContentControllerIdentifier), arg_message ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -1782,13 +1848,17 @@ - (nullable id)readValueOfType:(UInt8)type { case 129: return [FWFNSErrorData fromList:[self readValue]]; case 130: - return [FWFNSUrlRequestData fromList:[self readValue]]; + return [FWFNSHttpUrlResponseData fromList:[self readValue]]; case 131: - return [FWFWKFrameInfoData fromList:[self readValue]]; + return [FWFNSUrlRequestData fromList:[self readValue]]; case 132: - return [FWFWKNavigationActionData fromList:[self readValue]]; + return [FWFWKFrameInfoData fromList:[self readValue]]; case 133: + return [FWFWKNavigationActionData fromList:[self readValue]]; + case 134: return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; + case 135: + return [FWFWKNavigationResponseData fromList:[self readValue]]; default: return [super readValueOfType:type]; } @@ -1805,18 +1875,24 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[FWFNSErrorData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { + } else if ([value isKindOfClass:[FWFNSHttpUrlResponseData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { + } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { + } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:133]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + [self writeByte:134]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKNavigationResponseData class]]) { + [self writeByte:135]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1862,52 +1938,56 @@ - (void)didFinishNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier:(NSInteger)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.didFinishNavigation"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.didFinishNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_url ?: [NSNull null] ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_url ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier:(NSInteger)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion:(void (^)(FlutterError *_Nullable)) completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.didStartProvisionalNavigation"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.didStartProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_url ?: [NSNull null] ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_url ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier:(NSInteger)arg_webViewIdentifier @@ -1918,10 +1998,9 @@ - (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSInteger)arg_ FWFWKNavigationActionPolicyEnumData *_Nullable, FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ @@ -1939,60 +2018,107 @@ - (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSInteger)arg_ completion(output, nil); } } else { - completion(nil, createConnectionError(channelName)); + completion(nil, [FlutterError + errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } -- (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier - webViewIdentifier:(NSInteger)arg_webViewIdentifier - error:(FWFNSErrorData *)arg_error - completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.didFailNavigation"; +- (void)decidePolicyForNavigationResponseForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier + navigationResponse:(FWFWKNavigationResponseData *) + arg_navigationResponse + completion: + (void (^)( + FWFWKNavigationResponsePolicyEnumBox + *_Nullable, + FlutterError *_Nullable)) + completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_error ?: [NSNull null] ] + [channel sendMessage:@[ + @(arg_identifier), @(arg_webViewIdentifier), arg_navigationResponse ?: [NSNull null] + ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); } else { - completion(nil); + NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; + FWFWKNavigationResponsePolicyEnumBox *output = + outputAsNumber == nil ? nil + : [[FWFWKNavigationResponsePolicyEnumBox alloc] + initWithValue:[outputAsNumber integerValue]]; + completion(output, nil); } } else { - completion(createConnectionError(channelName)); + completion(nil, [FlutterError + errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } +- (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier + error:(FWFNSErrorData *)arg_error + completion:(void (^)(FlutterError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.didFailNavigation" + binaryMessenger:self.binaryMessenger + codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_error ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; +} - (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier:(NSInteger)arg_webViewIdentifier error:(FWFNSErrorData *)arg_error completion:(void (^)(FlutterError *_Nullable)) completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.didFailProvisionalNavigation"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.didFailProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_error ?: [NSNull null] ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), arg_error ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier: @@ -2000,40 +2126,41 @@ - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSInteger completion: (void (^)(FlutterError *_Nullable)) completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; -} -- (void) - didReceiveAuthenticationChallengeForDelegateWithIdentifier:(NSInteger)arg_identifier - webViewIdentifier:(NSInteger)arg_webViewIdentifier - challengeIdentifier:(NSInteger)arg_challengeIdentifier - completion: - (void (^)(FWFAuthenticationChallengeResponse - *_Nullable, - FlutterError *_Nullable)) + [channel + sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; +} +- (void) + didReceiveAuthenticationChallengeForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier + challengeIdentifier:(NSInteger)arg_challengeIdentifier + completion: + (void (^)(FWFAuthenticationChallengeResponse + *_Nullable, + FlutterError *_Nullable)) completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), @(arg_challengeIdentifier) ] @@ -2049,7 +2176,10 @@ - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSInteger completion(output, nil); } } else { - completion(nil, createConnectionError(channelName)); + completion(nil, [FlutterError + errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } @@ -2261,52 +2391,56 @@ - (void)observeValueForObjectWithIdentifier:(NSInteger)arg_identifier (NSArray *)arg_changeKeys changeValues:(NSArray *)arg_changeValues completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.observeValue"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFNSObjectFlutterApiGetCodec()]; - [channel sendMessage:@[ - @(arg_identifier), arg_keyPath ?: [NSNull null], @(arg_objectIdentifier), - arg_changeKeys ?: [NSNull null], arg_changeValues ?: [NSNull null] - ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.observeValue" + binaryMessenger:self.binaryMessenger + codec:FWFNSObjectFlutterApiGetCodec()]; + [channel + sendMessage:@[ + @(arg_identifier), arg_keyPath ?: [NSNull null], @(arg_objectIdentifier), + arg_changeKeys ?: [NSNull null], arg_changeValues ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)disposeObjectWithIdentifier:(NSInteger)arg_identifier completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.dispose"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFNSObjectFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.dispose" + binaryMessenger:self.binaryMessenger + codec:FWFNSObjectFlutterApiGetCodec()]; + [channel + sendMessage:@[ @(arg_identifier) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -2324,34 +2458,38 @@ - (nullable id)readValueOfType:(UInt8)type { case 131: return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; case 132: - return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; + return [FWFNSHttpUrlResponseData fromList:[self readValue]]; case 133: - return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; + return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; case 134: - return [FWFNSUrlRequestData fromList:[self readValue]]; + return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; case 135: - return [FWFObjectOrIdentifier fromList:[self readValue]]; + return [FWFNSUrlRequestData fromList:[self readValue]]; case 136: - return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; + return [FWFObjectOrIdentifier fromList:[self readValue]]; case 137: - return [FWFWKFrameInfoData fromList:[self readValue]]; + return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; case 138: - return [FWFWKMediaCaptureTypeData fromList:[self readValue]]; + return [FWFWKFrameInfoData fromList:[self readValue]]; case 139: - return [FWFWKNavigationActionData fromList:[self readValue]]; + return [FWFWKMediaCaptureTypeData fromList:[self readValue]]; case 140: - return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; + return [FWFWKNavigationActionData fromList:[self readValue]]; case 141: - return [FWFWKPermissionDecisionData fromList:[self readValue]]; + return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; case 142: - return [FWFWKScriptMessageData fromList:[self readValue]]; + return [FWFWKNavigationResponseData fromList:[self readValue]]; case 143: - return [FWFWKSecurityOriginData fromList:[self readValue]]; + return [FWFWKPermissionDecisionData fromList:[self readValue]]; case 144: - return [FWFWKUserScriptData fromList:[self readValue]]; + return [FWFWKScriptMessageData fromList:[self readValue]]; case 145: - return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; + return [FWFWKSecurityOriginData fromList:[self readValue]]; case 146: + return [FWFWKUserScriptData fromList:[self readValue]]; + case 147: + return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; + case 148: return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -2375,51 +2513,57 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { + } else if ([value isKindOfClass:[FWFNSHttpUrlResponseData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { + } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { [self writeByte:133]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { + } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { [self writeByte:134]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) { + } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:135]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { + } else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) { [self writeByte:136]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { + } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { [self writeByte:137]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) { + } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:138]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { + } else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) { [self writeByte:139]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:140]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { [self writeByte:141]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationResponseData class]]) { [self writeByte:142]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) { + } else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) { [self writeByte:143]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { + } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { [self writeByte:144]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) { [self writeByte:145]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { [self writeByte:146]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { + [self writeByte:147]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { + [self writeByte:148]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -3053,29 +3197,31 @@ - (void)onCreateWebViewForDelegateWithIdentifier:(NSInteger)arg_identifier configurationIdentifier:(NSInteger)arg_configurationIdentifier navigationAction:(FWFWKNavigationActionData *)arg_navigationAction completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.onCreateWebView"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFWKUIDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - @(arg_identifier), @(arg_webViewIdentifier), @(arg_configurationIdentifier), - arg_navigationAction ?: [NSNull null] - ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.onCreateWebView" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; + [channel + sendMessage:@[ + @(arg_identifier), @(arg_webViewIdentifier), @(arg_configurationIdentifier), + arg_navigationAction ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSInteger)arg_identifier webViewIdentifier:(NSInteger)arg_webViewIdentifier @@ -3086,43 +3232,44 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSInteger)arg_id (void (^)( FWFWKPermissionDecisionData *_Nullable, FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." - @"requestMediaCapturePermission"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFWKUIDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - @(arg_identifier), @(arg_webViewIdentifier), arg_origin ?: [NSNull null], - arg_frame ?: [NSNull null], arg_type ?: [NSNull null] - ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion(nil, [FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - FWFWKPermissionDecisionData *output = - reply[0] == [NSNull null] ? nil : reply[0]; - completion(output, nil); - } - } else { - completion(nil, createConnectionError(channelName)); - } - }]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"requestMediaCapturePermission" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; + [channel + sendMessage:@[ + @(arg_identifier), @(arg_webViewIdentifier), arg_origin ?: [NSNull null], + arg_frame ?: [NSNull null], arg_type ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + FWFWKPermissionDecisionData *output = reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, + [FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } - (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)arg_identifier message:(NSString *)arg_message frame:(FWFWKFrameInfoData *)arg_frame completion: (void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." - @"runJavaScriptAlertPanel"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFWKUIDelegateFlutterApiGetCodec()]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptAlertPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ] reply:^(NSArray *reply) { @@ -3135,7 +3282,9 @@ - (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)arg_identifi completion(nil); } } else { - completion(createConnectionError(channelName)); + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } @@ -3145,12 +3294,11 @@ - (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)arg_identi completion: (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." - @"runJavaScriptConfirmPanel"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFWKUIDelegateFlutterApiGetCodec()]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptConfirmPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ] reply:^(NSArray *reply) { @@ -3164,7 +3312,10 @@ - (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)arg_identi completion(output, nil); } } else { - completion(nil, createConnectionError(channelName)); + completion(nil, + [FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } @@ -3175,12 +3326,11 @@ - (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)arg_iden completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable)) completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." - @"runJavaScriptTextInputPanel"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFWKUIDelegateFlutterApiGetCodec()]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptTextInputPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ @(arg_identifier), arg_prompt ?: [NSNull null], arg_defaultText ?: [NSNull null], arg_frame ?: [NSNull null] @@ -3196,7 +3346,10 @@ - (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)arg_iden completion(output, nil); } } else { - completion(nil, createConnectionError(channelName)); + completion(nil, [FlutterError + errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); } }]; } @@ -3362,25 +3515,27 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } - (void)createWithIdentifier:(NSInteger)arg_identifier completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlFlutterApi.create"; - FlutterBasicMessageChannel *channel = - [FlutterBasicMessageChannel messageChannelWithName:channelName - binaryMessenger:self.binaryMessenger - codec:FWFNSUrlFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlFlutterApi.create" + binaryMessenger:self.binaryMessenger + codec:FWFNSUrlFlutterApiGetCodec()]; + [channel + sendMessage:@[ @(arg_identifier) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -3439,26 +3594,28 @@ - (void)scrollViewDidScrollWithIdentifier:(NSInteger)arg_identifier x:(double)arg_x y:(double)arg_y completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = @"dev.flutter.pigeon.webview_flutter_wkwebview." - @"UIScrollViewDelegateFlutterApi.scrollViewDidScroll"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"UIScrollViewDelegateFlutterApi.scrollViewDidScroll" binaryMessenger:self.binaryMessenger codec:FWFUIScrollViewDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_uiScrollViewIdentifier), @(arg_x), @(arg_y) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_uiScrollViewIdentifier), @(arg_x), @(arg_y) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -3527,29 +3684,31 @@ - (void)createWithIdentifier:(NSInteger)arg_identifier realm:(nullable NSString *)arg_realm authenticationMethod:(nullable NSString *)arg_authenticationMethod completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName: + @"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFNSUrlProtectionSpaceFlutterApiGetCodec()]; - [channel sendMessage:@[ - @(arg_identifier), arg_host ?: [NSNull null], arg_realm ?: [NSNull null], - arg_authenticationMethod ?: [NSNull null] - ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ + @(arg_identifier), arg_host ?: [NSNull null], arg_realm ?: [NSNull null], + arg_authenticationMethod ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end @@ -3575,25 +3734,27 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina - (void)createWithIdentifier:(NSInteger)arg_identifier protectionSpaceIdentifier:(NSInteger)arg_protectionSpaceIdentifier completion:(void (^)(FlutterError *_Nullable))completion { - NSString *channelName = - @"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlAuthenticationChallengeFlutterApi.create"; FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:channelName + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview." + @"NSUrlAuthenticationChallengeFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFNSUrlAuthenticationChallengeFlutterApiGetCodec()]; - [channel sendMessage:@[ @(arg_identifier), @(arg_protectionSpaceIdentifier) ] - reply:^(NSArray *reply) { - if (reply != nil) { - if (reply.count > 1) { - completion([FlutterError errorWithCode:reply[0] - message:reply[1] - details:reply[2]]); - } else { - completion(nil); - } - } else { - completion(createConnectionError(channelName)); - } - }]; + [channel + sendMessage:@[ @(arg_identifier), @(arg_protectionSpaceIdentifier) ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.h index 887c9f1b3d8b..f1994b7705b5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.h @@ -10,11 +10,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for WKHTTPCookieStore. - * - * Handles creating WKHTTPCookieStore that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKHTTPCookieStore. +/// +/// Handles creating WKHTTPCookieStore that intercommunicate with a paired Dart object. @interface FWFHTTPCookieStoreHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h index 5dec08055ce5..f88370598093 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h @@ -8,89 +8,76 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^FWFOnDeallocCallback)(long identifier); -/** - * Maintains instances used to communicate with the corresponding objects in Dart. - * - * 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 `removeStrongReferenceWithIdentifier:` and the weak reference is deallocated, - * the `deallocCallback` is made 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 `identifierForInstance:identifierWillBePassedToFlutter:` with - * `identifierWillBePassedToFlutter` set to YES), the strong reference to the instance is recreated. - * The strong reference will then need to be removed manually again. - * - * Accessing and inserting to an InstanceManager is thread safe. - */ +/// Maintains instances used to communicate with the corresponding objects in Dart. +/// +/// 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 `removeStrongReferenceWithIdentifier:` and the weak reference is deallocated, +/// the `deallocCallback` is made 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 `identifierForInstance:identifierWillBePassedToFlutter:` with +/// `identifierWillBePassedToFlutter` set to YES), the strong reference to the instance is +/// recreated. The strong reference will then need to be removed manually again. +/// +/// Accessing and inserting to an InstanceManager is thread safe. @interface FWFInstanceManager : NSObject @property(readonly) FWFOnDeallocCallback deallocCallback; - (instancetype)initWithDeallocCallback:(FWFOnDeallocCallback)callback; + // TODO(bparrishMines): Pairs should not be able to be overwritten and this feature // should be replaced with a call to clear the manager in the event of a hot restart. -/** - * Adds a new instance that was instantiated from Dart. - * - * If an instance or identifier has already been added, it will be replaced by the new values. The - * Dart InstanceManager is considered the source of truth and has the capability to overwrite stored - * pairs in response to hot restarts. - * - * @param instance The instance to be stored. - * @param instanceIdentifier The identifier to be paired with instance. This value must be >= 0. - */ +/// Adds a new instance that was instantiated from Dart. +/// +/// If an instance or identifier has already been added, it will be replaced by the new values. The +/// Dart InstanceManager is considered the source of truth and has the capability to overwrite +/// stored pairs in response to hot restarts. +/// +/// @param instance The instance to be stored. +/// @param instanceIdentifier The identifier to be paired with instance. This value must be >= 0. - (void)addDartCreatedInstance:(NSObject *)instance withIdentifier:(long)instanceIdentifier; -/** - * Adds a new instance that was instantiated from the host platform. - * - * @param instance The instance to be stored. - * @return The unique identifier stored with instance. - */ +/// Adds a new instance that was instantiated from the host platform. +/// +/// @param instance The instance to be stored. +/// @return The unique identifier stored with instance. - (long)addHostCreatedInstance:(nonnull NSObject *)instance; -/** - * Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from - * the manager. - * - * @param instanceIdentifier The identifier paired to an instance. - * - * @return The removed instance if the manager contains the given instanceIdentifier, otherwise - * nil. - */ +/// Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from +/// the manager. +/// +/// @param instanceIdentifier The identifier paired to an instance. +/// +/// @return The removed instance if the manager contains the given instanceIdentifier, otherwise +/// nil. - (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier; -/** - * Retrieves the instance associated with identifier. - * - * @param instanceIdentifier The identifier paired to an instance. - * - * @return The instance associated with `instanceIdentifier` if the manager contains the value, - * otherwise nil. - */ +/// Retrieves the instance associated with identifier. +/// +/// @param instanceIdentifier The identifier paired to an instance. +/// +/// @return The instance associated with `instanceIdentifier` if the manager contains the value, +/// otherwise nil. - (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier; -/** - * Retrieves the identifier paired with an instance. - * - * If the manager contains `instance`, as a strong or weak reference, the strong reference to - * `instance` will be recreated and will need to be removed again with - * `removeInstanceWithIdentifier:`. - * - * This method also expects the Dart `InstanceManager` to have, or recreate, a weak reference to the - * instance the identifier is associated with once it receives it. - * - * @param instance An instance that may be stored in the manager. - * - * @return The identifier associated with `instance` if the manager contains the value, otherwise - * NSNotFound. - */ +/// Retrieves the identifier paired with an instance. +/// +/// If the manager contains `instance`, as a strong or weak reference, the strong reference to +/// `instance` will be recreated and will need to be removed again with +/// `removeInstanceWithIdentifier:`. +/// +/// This method also expects the Dart `InstanceManager` to have, or recreate, a weak reference to +/// the instance the identifier is associated with once it receives it. +/// +/// @param instance An instance that may be stored in the manager. +/// +/// @return The identifier associated with `instance` if the manager contains the value, otherwise +/// NSNotFound. - (long)identifierWithStrongReferenceForInstance:(nonnull NSObject *)instance; -/** - * Returns whether this manager contains the given `instance`. - * - * @return Whether this manager contains the given `instance`. - */ +/// Returns whether this manager contains the given `instance`. +/// +/// @return Whether this manager contains the given `instance`. - (BOOL)containsInstance:(nonnull NSObject *)instance; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h index 63480ce18018..20d1e4847da7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h @@ -7,24 +7,18 @@ NS_ASSUME_NONNULL_BEGIN @interface FWFInstanceManager () -/** - * The next identifier that will be used for a host-created instance. - */ +/// The next identifier that will be used for a host-created instance. @property long nextIdentifier; -/** - * The number of instances stored as a strong reference. - * - * Added for debugging purposes. - */ +/// The number of instances stored as a strong reference. +/// +/// Added for debugging purposes. - (NSUInteger)strongInstanceCount; -/** - * The number of instances stored as a weak reference. - * - * Added for debugging purposes. NSMapTables that store keys or objects as weak reference will be - * reclaimed nondeterministically. - */ +/// The number of instances stored as a weak reference. +/// +/// Added for debugging purposes. NSMapTables that store keys or objects as weak reference will be +/// reclaimed nondeterministically. - (NSUInteger)weakInstanceCount; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h index 90e55417cd1b..fb4e076095d9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h @@ -12,19 +12,15 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for WKNavigationDelegate. - * - * Handles making callbacks to Dart for a WKNavigationDelegate. - */ +/// Flutter api implementation for WKNavigationDelegate. +/// +/// Handles making callbacks to Dart for a WKNavigationDelegate. @interface FWFNavigationDelegateFlutterApiImpl : FWFWKNavigationDelegateFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Implementation of WKNavigationDelegate for FWFNavigationDelegateHostApiImpl. - */ +/// Implementation of WKNavigationDelegate for FWFNavigationDelegateHostApiImpl. @interface FWFNavigationDelegate : FWFObject @property(readonly, nonnull, nonatomic) FWFNavigationDelegateFlutterApiImpl *navigationDelegateAPI; @@ -32,11 +28,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for WKNavigationDelegate. - * - * Handles creating WKNavigationDelegate that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKNavigationDelegate. +/// +/// Handles creating WKNavigationDelegate that intercommunicate with a paired Dart object. @interface FWFNavigationDelegateHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m index db8c4562748f..2718702d4b93 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m @@ -72,6 +72,24 @@ - (void)didStartProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instan completion:completion]; } +- (void)decidePolicyForNavigationResponseForDelegate:(FWFNavigationDelegate *)instance + webView:(WKWebView *)webView + navigationResponse:(WKNavigationResponse *)navigationResponse + completion: + (void (^)(FWFWKNavigationResponsePolicyEnumBox *, + FlutterError *_Nullable))completion { + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; + FWFWKNavigationResponseData *navigationResponseData = + FWFWKNavigationResponseDataFromNativeNavigationResponse(navigationResponse); + [self + decidePolicyForNavigationResponseForDelegateWithIdentifier:[self + identifierForDelegate:instance] + webViewIdentifier:webViewIdentifier + navigationResponse:navigationResponseData + completion:completion]; +} + - (void)didFailNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView error:(NSError *)error @@ -190,6 +208,26 @@ - (void)webView:(WKWebView *)webView }]; } +- (void)webView:(WKWebView *)webView + decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse + decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { + [self.navigationDelegateAPI + decidePolicyForNavigationResponseForDelegate:self + webView:webView + navigationResponse:navigationResponse + completion:^(FWFWKNavigationResponsePolicyEnumBox *policy, + FlutterError *error) { + NSAssert(!error, @"%@", error); + if (!error) { + decisionHandler( + FWFNativeWKNavigationResponsePolicyFromEnum( + policy.value)); + } else { + decisionHandler(WKNavigationResponsePolicyCancel); + } + }]; +} + - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h index c1b4355330d4..08080eb5bb8c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h @@ -9,11 +9,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for NSObject. - * - * Handles making callbacks to Dart for an NSObject. - */ +/// Flutter api implementation for NSObject. +/// +/// Handles making callbacks to Dart for an NSObject. @interface FWFObjectFlutterApiImpl : FWFNSObjectFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @@ -25,9 +23,7 @@ NS_ASSUME_NONNULL_BEGIN completion:(void (^)(FlutterError *_Nullable))completion; @end -/** - * Implementation of NSObject for FWFObjectHostApiImpl. - */ +/// Implementation of NSObject for FWFObjectHostApiImpl. @interface FWFObject : NSObject @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; @@ -35,11 +31,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for NSObject. - * - * Handles creating NSObject that intercommunicate with a paired Dart object. - */ +/// Host api implementation for NSObject. +/// +/// Handles creating NSObject that intercommunicate with a paired Dart object. @interface FWFObjectHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.h index de2d26491a58..136dccaec8cb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.h @@ -10,11 +10,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for WKPreferences. - * - * Handles creating WKPreferences that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKPreferences. +/// +/// Handles creating WKPreferences that intercommunicate with a paired Dart object. @interface FWFPreferencesHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.h index 9c5769e4658b..91e6aa01392f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.h @@ -11,19 +11,15 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for WKScriptMessageHandler. - * - * Handles making callbacks to Dart for a WKScriptMessageHandler. - */ +/// Flutter api implementation for WKScriptMessageHandler. +/// +/// Handles making callbacks to Dart for a WKScriptMessageHandler. @interface FWFScriptMessageHandlerFlutterApiImpl : FWFWKScriptMessageHandlerFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Implementation of WKScriptMessageHandler for FWFScriptMessageHandlerHostApiImpl. - */ +/// Implementation of WKScriptMessageHandler for FWFScriptMessageHandlerHostApiImpl. @interface FWFScriptMessageHandler : FWFObject @property(readonly, nonnull, nonatomic) FWFScriptMessageHandlerFlutterApiImpl *scriptMessageHandlerAPI; @@ -32,11 +28,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for WKScriptMessageHandler. - * - * Handles creating WKScriptMessageHandler that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKScriptMessageHandler. +/// +/// Handles creating WKScriptMessageHandler that intercommunicate with a paired Dart object. @interface FWFScriptMessageHandlerHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewDelegateHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewDelegateHostApi.h index 5324725a8fbe..ec2237c6a0dd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewDelegateHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewDelegateHostApi.h @@ -11,20 +11,16 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for UIScrollViewDelegate. - * - * Handles making callbacks to Dart for a UIScrollViewDelegate. - */ +/// Flutter api implementation for UIScrollViewDelegate. +/// +/// Handles making callbacks to Dart for a UIScrollViewDelegate. @interface FWFScrollViewDelegateFlutterApiImpl : FWFUIScrollViewDelegateFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Implementation of WKUIScrollViewDelegate for FWFUIScrollViewDelegateHostApiImpl. - */ +/// Implementation of WKUIScrollViewDelegate for FWFUIScrollViewDelegateHostApiImpl. @interface FWFScrollViewDelegate : FWFObject @property(readonly, nonnull, nonatomic) FWFScrollViewDelegateFlutterApiImpl *scrollViewDelegateAPI; @@ -33,11 +29,9 @@ NS_ASSUME_NONNULL_BEGIN @end -/** - * Host api implementation for UIScrollViewDelegate. - * - * Handles creating UIScrollView that intercommunicate with a paired Dart object. - */ +/// Host api implementation for UIScrollViewDelegate. +/// +/// Handles creating UIScrollView that intercommunicate with a paired Dart object. @interface FWFScrollViewDelegateHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.h index 25f373f374e3..7d87e4d561f9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.h @@ -10,11 +10,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for UIScrollView. - * - * Handles creating UIScrollView that intercommunicate with a paired Dart object. - */ +/// Host api implementation for UIScrollView. +/// +/// Handles creating UIScrollView that intercommunicate with a paired Dart object. @interface FWFScrollViewHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.h index 7b6b4eec9b8e..fd685c7b4b97 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.h @@ -12,11 +12,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for WKUIDelegate. - * - * Handles making callbacks to Dart for a WKUIDelegate. - */ +/// Flutter api implementation for WKUIDelegate. +/// +/// Handles making callbacks to Dart for a WKUIDelegate. @interface FWFUIDelegateFlutterApiImpl : FWFWKUIDelegateFlutterApi @property(readonly, nonatomic) FWFWebViewConfigurationFlutterApiImpl *webViewConfigurationFlutterApi; @@ -25,9 +23,7 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Implementation of WKUIDelegate for FWFUIDelegateHostApiImpl. - */ +/// Implementation of WKUIDelegate for FWFUIDelegateHostApiImpl. @interface FWFUIDelegate : FWFObject @property(readonly, nonnull, nonatomic) FWFUIDelegateFlutterApiImpl *UIDelegateAPI; @@ -35,11 +31,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for WKUIDelegate. - * - * Handles creating WKUIDelegate that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKUIDelegate. +/// +/// Handles creating WKUIDelegate that intercommunicate with a paired Dart object. @interface FWFUIDelegateHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.h index 82edd6b742ca..f63119738a82 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.h @@ -9,11 +9,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for UIView. - * - * Handles creating UIView that intercommunicate with a paired Dart object. - */ +/// Host api implementation for UIView. +/// +/// Handles creating UIView that intercommunicate with a paired Dart object. @interface FWFUIViewHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLAuthenticationChallengeHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLAuthenticationChallengeHostApi.h index 6100b703c29b..2a16ff486b6d 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLAuthenticationChallengeHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLAuthenticationChallengeHostApi.h @@ -9,22 +9,16 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter API implementation for `NSURLAuthenticationChallenge`. - * - * This class may handle instantiating and adding Dart instances that are attached to a native - * instance or sending callback methods from an overridden native class. - */ +/// Flutter API implementation for `NSURLAuthenticationChallenge`. +/// +/// This class may handle instantiating and adding Dart instances that are attached to a native +/// instance or sending callback methods from an overridden native class. @interface FWFURLAuthenticationChallengeFlutterApiImpl : NSObject -/** - * The Flutter API used to send messages back to Dart. - */ +/// The Flutter API used to send messages back to Dart. @property FWFNSUrlAuthenticationChallengeFlutterApi *api; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; -/** - * Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - */ +/// Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - (void)createWithInstance:(NSURLAuthenticationChallenge *)instance protectionSpace:(NSURLProtectionSpace *)protectionSpace completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLCredentialHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLCredentialHostApi.h index fe9b3d0d8d50..3c119e552e09 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLCredentialHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLCredentialHostApi.h @@ -10,12 +10,10 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host API implementation for `NSURLCredential`. - * - * This class may handle instantiating and adding native object instances that are attached to a - * Dart instance or method calls on the associated native class or an instance of the class. - */ +/// Host API implementation for `NSURLCredential`. +/// +/// This class may handle instantiating and adding native object instances that are attached to a +/// Dart instance or method calls on the associated native class or an instance of the class. @interface FWFURLCredentialHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.h index 248f0b7f20b7..919dcc5b3cf2 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.h @@ -9,33 +9,26 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host API implementation for `NSURL`. - * - * This class may handle instantiating and adding native object instances that are attached to a - * Dart instance or method calls on the associated native class or an instance of the class. - */ +/// Host API implementation for `NSURL`. +/// +/// This class may handle instantiating and adding native object instances that are attached to a +/// Dart instance or method calls on the associated native class or an instance of the class. @interface FWFURLHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Flutter API implementation for `NSURL`. - * - * This class may handle instantiating and adding Dart instances that are attached to a native - * instance or sending callback methods from an overridden native class. - */ +/// Flutter API implementation for `NSURL`. +/// +/// This class may handle instantiating and adding Dart instances that are attached to a native +/// instance or sending callback methods from an overridden native class. @interface FWFURLFlutterApiImpl : NSObject -/** - * The Flutter API used to send messages back to Dart. - */ +/// The Flutter API used to send messages back to Dart. @property FWFNSUrlFlutterApi *api; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; -/** - * Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - */ + +/// Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - (void)create:(NSURL *)instance completion:(void (^)(FlutterError *_Nullable))completion; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLProtectionSpaceHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLProtectionSpaceHostApi.h index 5e57ab5404ab..a4f7c537f1b5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLProtectionSpaceHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLProtectionSpaceHostApi.h @@ -9,22 +9,16 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter API implementation for `NSURLProtectionSpace`. - * - * This class may handle instantiating and adding Dart instances that are attached to a native - * instance or sending callback methods from an overridden native class. - */ +/// Flutter API implementation for `NSURLProtectionSpace`. +/// +/// This class may handle instantiating and adding Dart instances that are attached to a native +/// instance or sending callback methods from an overridden native class. @interface FWFURLProtectionSpaceFlutterApiImpl : NSObject -/** - * The Flutter API used to send messages back to Dart. - */ +/// The Flutter API used to send messages back to Dart. @property FWFNSUrlProtectionSpaceFlutterApi *api; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; -/** - * Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - */ +/// Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`. - (void)createWithInstance:(NSURLProtectionSpace *)instance host:(nullable NSString *)host realm:(nullable NSString *)realm diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.h index f0e5a1383ac3..8ca3b69b6a39 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.h @@ -10,11 +10,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for WKUserContentController. - * - * Handles creating WKUserContentController that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKUserContentController. +/// +/// Handles creating WKUserContentController that intercommunicate with a paired Dart object. @interface FWFUserContentControllerHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.h index 363fe6ae6664..c11ce1f29567 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.h @@ -11,11 +11,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Flutter api implementation for WKWebViewConfiguration. - * - * Handles making callbacks to Dart for a WKWebViewConfiguration. - */ +/// Flutter api implementation for WKWebViewConfiguration. +/// +/// Handles making callbacks to Dart for a WKWebViewConfiguration. @interface FWFWebViewConfigurationFlutterApiImpl : FWFWKWebViewConfigurationFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @@ -24,9 +22,7 @@ NS_ASSUME_NONNULL_BEGIN completion:(void (^)(FlutterError *_Nullable))completion; @end -/** - * Implementation of WKWebViewConfiguration for FWFWebViewConfigurationHostApiImpl. - */ +/// Implementation of WKWebViewConfiguration for FWFWebViewConfigurationHostApiImpl. @interface FWFWebViewConfiguration : WKWebViewConfiguration @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; @@ -34,11 +30,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for WKWebViewConfiguration. - * - * Handles creating WKWebViewConfiguration that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKWebViewConfiguration. +/// +/// Handles creating WKWebViewConfiguration that intercommunicate with a paired Dart object. @interface FWFWebViewConfigurationHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h index 297f8c37ec3e..b316f333cdde 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h @@ -8,28 +8,24 @@ NS_ASSUME_NONNULL_BEGIN -/** - * App and package facing native API provided by the `webview_flutter_wkwebview` plugin. - * - * This class follows the convention of breaking changes of the Dart API, which means that any - * changes to the class that are not backwards compatible will only be made with a major version - * change of the plugin. Native code other than this external API does not follow breaking change - * conventions, so app or plugin clients should not use any other native APIs. - */ +/// App and package facing native API provided by the `webview_flutter_wkwebview` plugin. +/// +/// This class follows the convention of breaking changes of the Dart API, which means that any +/// changes to the class that are not backwards compatible will only be made with a major version +/// change of the plugin. Native code other than this external API does not follow breaking change +/// conventions, so app or plugin clients should not use any other native APIs. @interface FWFWebViewFlutterWKWebViewExternalAPI : NSObject -/** - * Retrieves the `WKWebView` that is associated with `identifier`. - * - * See the Dart method `WebKitWebViewController.webViewIdentifier` to get the identifier of an - * underlying `WKWebView`. - * - * @param identifier The associated identifier of the `WebView`. - * @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. If - * the registry doesn't contain an attached instance of `FLTWebViewFlutterPlugin`, - * this method returns nil. - * @return The `WKWebView` associated with `identifier` or nil if a `WKWebView` instance associated - * with `identifier` could not be found. - */ +/// Retrieves the `WKWebView` that is associated with `identifier`. +/// +/// See the Dart method `WebKitWebViewController.webViewIdentifier` to get the identifier of an +/// underlying `WKWebView`. +/// +/// @param identifier The associated identifier of the `WebView`. +/// @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. If +/// the registry doesn't contain an attached instance of `FLTWebViewFlutterPlugin`, +/// this method returns nil. +/// @return The `WKWebView` associated with `identifier` or nil if a `WKWebView` instance associated +/// with `identifier` could not be found. + (nullable WKWebView *)webViewForIdentifier:(long)identifier withPluginRegistry:(id)registry; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.h index f1bb59bcb9ae..037ea11f8494 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.h @@ -11,18 +11,14 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A set of Flutter and Dart assets used by a `FlutterEngine` to initialize execution. - * - * Default implementation delegates methods to FlutterDartProject. - */ +/// A set of Flutter and Dart assets used by a `FlutterEngine` to initialize execution. +/// +/// Default implementation delegates methods to FlutterDartProject. @interface FWFAssetManager : NSObject - (NSString *)lookupKeyForAsset:(NSString *)asset; @end -/** - * Implementation of WKWebView that can be used as a FlutterPlatformView. - */ +/// Implementation of WKWebView that can be used as a FlutterPlatformView. @interface FWFWebView : WKWebView @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; @@ -32,11 +28,9 @@ NS_ASSUME_NONNULL_BEGIN instanceManager:(FWFInstanceManager *)instanceManager; @end -/** - * Host api implementation for WKWebView. - * - * Handles creating WKWebViews that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKWebView. +/// +/// Handles creating WKWebViews that intercommunicate with a paired Dart object. @interface FWFWebViewHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.h index 72f00e032ee4..256c3f9d41f6 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.h @@ -10,11 +10,9 @@ NS_ASSUME_NONNULL_BEGIN -/** - * Host api implementation for WKWebsiteDataStore. - * - * Handles creating WKWebsiteDataStore that intercommunicate with a paired Dart object. - */ +/// Host api implementation for WKWebsiteDataStore. +/// +/// Handles creating WKWebsiteDataStore that intercommunicate with a paired Dart object. @interface FWFWebsiteDataStoreHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart index 6e1162a97caa..65f55dbbe3ff 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.1.2), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), 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, unused_shown_name, unnecessary_import @@ -11,13 +11,6 @@ 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".', - ); -} - List wrapResponse( {Object? result, PlatformException? error, bool empty = false}) { if (empty) { @@ -101,6 +94,14 @@ enum WKNavigationActionPolicyEnum { cancel, } +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +enum WKNavigationResponsePolicyEnum { + allow, + cancel, +} + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -490,6 +491,30 @@ class NSUrlRequestData { } } +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +class NSHttpUrlResponseData { + NSHttpUrlResponseData({ + required this.statusCode, + }); + + int statusCode; + + Object encode() { + return [ + statusCode, + ]; + } + + static NSHttpUrlResponseData decode(Object result) { + result as List; + return NSHttpUrlResponseData( + statusCode: result[0]! as int, + ); + } +} + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -561,6 +586,35 @@ class WKNavigationActionData { } } +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +class WKNavigationResponseData { + WKNavigationResponseData({ + required this.response, + required this.forMainFrame, + }); + + NSHttpUrlResponseData response; + + bool forMainFrame; + + Object encode() { + return [ + response.encode(), + forMainFrame, + ]; + } + + static WKNavigationResponseData decode(Object result) { + result as List; + return WKNavigationResponseData( + response: NSHttpUrlResponseData.decode(result[0]! as List), + forMainFrame: result[1]! as bool, + ); + } +} + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -813,18 +867,18 @@ class WKWebsiteDataStoreHostApi { Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -837,17 +891,17 @@ class WKWebsiteDataStoreHostApi { } Future createDefaultDataStore(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createDefaultDataStore'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createDefaultDataStore', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -863,20 +917,20 @@ class WKWebsiteDataStoreHostApi { int arg_identifier, List arg_dataTypes, double arg_modificationTimeInSecondsSinceEpoch) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.removeDataOfTypes'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.removeDataOfTypes', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, arg_dataTypes, arg_modificationTimeInSecondsSinceEpoch ]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -908,17 +962,17 @@ class UIViewHostApi { static const MessageCodec codec = StandardMessageCodec(); Future setBackgroundColor(int arg_identifier, int? arg_value) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setBackgroundColor'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setBackgroundColor', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_value]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -931,17 +985,17 @@ class UIViewHostApi { } Future setOpaque(int arg_identifier, bool arg_opaque) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setOpaque'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setOpaque', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_opaque]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -969,18 +1023,18 @@ class UIScrollViewHostApi { Future createFromWebView( int arg_identifier, int arg_webViewIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.createFromWebView'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.createFromWebView', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_webViewIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -993,17 +1047,17 @@ class UIScrollViewHostApi { } Future> getContentOffset(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.getContentOffset'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.getContentOffset', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1021,17 +1075,17 @@ class UIScrollViewHostApi { } Future scrollBy(int arg_identifier, double arg_x, double arg_y) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.scrollBy'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.scrollBy', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_x, arg_y]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1045,17 +1099,17 @@ class UIScrollViewHostApi { Future setContentOffset( int arg_identifier, double arg_x, double arg_y) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.setContentOffset'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.setContentOffset', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_x, arg_y]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1069,18 +1123,18 @@ class UIScrollViewHostApi { Future setDelegate( int arg_identifier, int? arg_uiScrollViewDelegateIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.setDelegate'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.setDelegate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_uiScrollViewDelegateIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1131,17 +1185,17 @@ class WKWebViewConfigurationHostApi { _WKWebViewConfigurationHostApiCodec(); Future create(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1155,18 +1209,18 @@ class WKWebViewConfigurationHostApi { Future createFromWebView( int arg_identifier, int arg_webViewIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.createFromWebView'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.createFromWebView', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_webViewIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1180,17 +1234,17 @@ class WKWebViewConfigurationHostApi { Future setAllowsInlineMediaPlayback( int arg_identifier, bool arg_allow) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_allow]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1204,17 +1258,17 @@ class WKWebViewConfigurationHostApi { Future setLimitsNavigationsToAppBoundDomains( int arg_identifier, bool arg_limit) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setLimitsNavigationsToAppBoundDomains'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setLimitsNavigationsToAppBoundDomains', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_limit]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1228,17 +1282,17 @@ class WKWebViewConfigurationHostApi { Future setMediaTypesRequiringUserActionForPlayback(int arg_identifier, List arg_types) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_types]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1335,18 +1389,18 @@ class WKUserContentControllerHostApi { Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.createFromWebViewConfiguration'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.createFromWebViewConfiguration', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1360,18 +1414,18 @@ class WKUserContentControllerHostApi { Future addScriptMessageHandler( int arg_identifier, int arg_handlerIdentifier, String arg_name) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addScriptMessageHandler'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addScriptMessageHandler', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_handlerIdentifier, arg_name]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1385,17 +1439,17 @@ class WKUserContentControllerHostApi { Future removeScriptMessageHandler( int arg_identifier, String arg_name) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeScriptMessageHandler'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeScriptMessageHandler', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_name]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1408,17 +1462,17 @@ class WKUserContentControllerHostApi { } Future removeAllScriptMessageHandlers(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllScriptMessageHandlers'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllScriptMessageHandlers', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1432,17 +1486,17 @@ class WKUserContentControllerHostApi { Future addUserScript( int arg_identifier, WKUserScriptData arg_userScript) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addUserScript'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addUserScript', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_userScript]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1455,17 +1509,17 @@ class WKUserContentControllerHostApi { } Future removeAllUserScripts(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllUserScripts'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllUserScripts', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1493,18 +1547,18 @@ class WKPreferencesHostApi { Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.createFromWebViewConfiguration'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.createFromWebViewConfiguration', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1518,17 +1572,17 @@ class WKPreferencesHostApi { Future setJavaScriptEnabled( int arg_identifier, bool arg_enabled) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.setJavaScriptEnabled'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.setJavaScriptEnabled', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_enabled]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1555,17 +1609,17 @@ class WKScriptMessageHandlerHostApi { static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKScriptMessageHandlerHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKScriptMessageHandlerHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1665,17 +1719,17 @@ class WKNavigationDelegateHostApi { static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -1698,18 +1752,24 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { } else if (value is NSErrorData) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is NSUrlRequestData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(133); writeValue(buffer, value.encode()); + } else if (value is WKNavigationActionPolicyEnumData) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is WKNavigationResponseData) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1723,13 +1783,17 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { case 129: return NSErrorData.decode(readValue(buffer)!); case 130: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 131: - return WKFrameInfoData.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 132: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 133: + return WKNavigationActionData.decode(readValue(buffer)!); + case 134: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + case 135: + return WKNavigationResponseData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -1753,6 +1817,11 @@ abstract class WKNavigationDelegateFlutterApi { int webViewIdentifier, WKNavigationActionData navigationAction); + Future decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse); + void didFailNavigation( int identifier, int webViewIdentifier, NSErrorData error); @@ -1867,6 +1936,42 @@ abstract class WKNavigationDelegateFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse 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.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null int.'); + final int? arg_webViewIdentifier = (args[1] as int?); + assert(arg_webViewIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null int.'); + final WKNavigationResponseData? arg_navigationResponse = + (args[2] as WKNavigationResponseData?); + assert(arg_navigationResponse != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null WKNavigationResponseData.'); + try { + final WKNavigationResponsePolicyEnum output = + await api.decidePolicyForNavigationResponse(arg_identifier!, + arg_webViewIdentifier!, arg_navigationResponse!); + return wrapResponse(result: output.index); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didFailNavigation', @@ -2041,17 +2146,17 @@ class NSObjectHostApi { static const MessageCodec codec = _NSObjectHostApiCodec(); Future dispose(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.dispose'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.dispose', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2068,13 +2173,10 @@ class NSObjectHostApi { int arg_observerIdentifier, String arg_keyPath, List arg_options) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.addObserver'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.addObserver', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, arg_observerIdentifier, @@ -2082,7 +2184,10 @@ class NSObjectHostApi { arg_options ]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2096,18 +2201,18 @@ class NSObjectHostApi { Future removeObserver(int arg_identifier, int arg_observerIdentifier, String arg_keyPath) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.removeObserver'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.removeObserver', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( [arg_identifier, arg_observerIdentifier, arg_keyPath]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2253,51 +2358,57 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec { } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueChangeKeyEnumData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueObservingOptionsEnumData) { + } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is ObjectOrIdentifier) { + } else if (value is NSUrlRequestData) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is WKAudiovisualMediaTypeEnumData) { + } else if (value is ObjectOrIdentifier) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is WKMediaCaptureTypeData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKMediaCaptureTypeData) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is WKPermissionDecisionData) { + } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is WKScriptMessageData) { + } else if (value is WKNavigationResponseData) { buffer.putUint8(142); writeValue(buffer, value.encode()); - } else if (value is WKSecurityOriginData) { + } else if (value is WKPermissionDecisionData) { buffer.putUint8(143); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptData) { + } else if (value is WKScriptMessageData) { buffer.putUint8(144); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptInjectionTimeEnumData) { + } else if (value is WKSecurityOriginData) { buffer.putUint8(145); writeValue(buffer, value.encode()); - } else if (value is WKWebsiteDataTypeEnumData) { + } else if (value is WKUserScriptData) { buffer.putUint8(146); writeValue(buffer, value.encode()); + } else if (value is WKUserScriptInjectionTimeEnumData) { + buffer.putUint8(147); + writeValue(buffer, value.encode()); + } else if (value is WKWebsiteDataTypeEnumData) { + buffer.putUint8(148); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -2315,34 +2426,38 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec { case 131: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 132: - return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 133: - return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); + return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 134: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 135: - return ObjectOrIdentifier.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 136: - return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); + return ObjectOrIdentifier.decode(readValue(buffer)!); case 137: - return WKFrameInfoData.decode(readValue(buffer)!); + return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 138: - return WKMediaCaptureTypeData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 139: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKMediaCaptureTypeData.decode(readValue(buffer)!); case 140: - return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + return WKNavigationActionData.decode(readValue(buffer)!); case 141: - return WKPermissionDecisionData.decode(readValue(buffer)!); + return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 142: - return WKScriptMessageData.decode(readValue(buffer)!); + return WKNavigationResponseData.decode(readValue(buffer)!); case 143: - return WKSecurityOriginData.decode(readValue(buffer)!); + return WKPermissionDecisionData.decode(readValue(buffer)!); case 144: - return WKUserScriptData.decode(readValue(buffer)!); + return WKScriptMessageData.decode(readValue(buffer)!); case 145: - return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + return WKSecurityOriginData.decode(readValue(buffer)!); case 146: + return WKUserScriptData.decode(readValue(buffer)!); + case 147: + return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + case 148: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -2365,18 +2480,18 @@ class WKWebViewHostApi { Future create( int arg_identifier, int arg_configurationIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2390,18 +2505,18 @@ class WKWebViewHostApi { Future setUIDelegate( int arg_identifier, int? arg_uiDelegateIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setUIDelegate'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setUIDelegate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_uiDelegateIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2415,18 +2530,18 @@ class WKWebViewHostApi { Future setNavigationDelegate( int arg_identifier, int? arg_navigationDelegateIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setNavigationDelegate'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setNavigationDelegate', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_navigationDelegateIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2439,17 +2554,17 @@ class WKWebViewHostApi { } Future getUrl(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getUrl'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getUrl', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2462,17 +2577,17 @@ class WKWebViewHostApi { } Future getEstimatedProgress(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getEstimatedProgress'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getEstimatedProgress', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2491,17 +2606,17 @@ class WKWebViewHostApi { Future loadRequest( int arg_identifier, NSUrlRequestData arg_request) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadRequest'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadRequest', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_request]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2515,18 +2630,18 @@ class WKWebViewHostApi { Future loadHtmlString( int arg_identifier, String arg_string, String? arg_baseUrl) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadHtmlString'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadHtmlString', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_string, arg_baseUrl]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2540,18 +2655,18 @@ class WKWebViewHostApi { Future loadFileUrl( int arg_identifier, String arg_url, String arg_readAccessUrl) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFileUrl'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFileUrl', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_url, arg_readAccessUrl]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2564,17 +2679,17 @@ class WKWebViewHostApi { } Future loadFlutterAsset(int arg_identifier, String arg_key) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFlutterAsset'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFlutterAsset', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_key]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2587,17 +2702,17 @@ class WKWebViewHostApi { } Future canGoBack(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoBack'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoBack', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2615,17 +2730,17 @@ class WKWebViewHostApi { } Future canGoForward(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoForward'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoForward', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2643,17 +2758,17 @@ class WKWebViewHostApi { } Future goBack(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goBack'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goBack', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2666,17 +2781,17 @@ class WKWebViewHostApi { } Future goForward(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goForward'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goForward', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2689,17 +2804,17 @@ class WKWebViewHostApi { } Future reload(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.reload'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.reload', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2712,17 +2827,17 @@ class WKWebViewHostApi { } Future getTitle(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getTitle'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getTitle', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2736,17 +2851,17 @@ class WKWebViewHostApi { Future setAllowsBackForwardNavigationGestures( int arg_identifier, bool arg_allow) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setAllowsBackForwardNavigationGestures'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setAllowsBackForwardNavigationGestures', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_allow]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2760,17 +2875,17 @@ class WKWebViewHostApi { Future setCustomUserAgent( int arg_identifier, String? arg_userAgent) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setCustomUserAgent'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setCustomUserAgent', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_userAgent]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2784,18 +2899,18 @@ class WKWebViewHostApi { Future evaluateJavaScript( int arg_identifier, String arg_javaScriptString) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.evaluateJavaScript'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.evaluateJavaScript', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_javaScriptString]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2808,17 +2923,17 @@ class WKWebViewHostApi { } Future setInspectable(int arg_identifier, bool arg_inspectable) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setInspectable'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setInspectable', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_inspectable]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2831,17 +2946,17 @@ class WKWebViewHostApi { } Future getCustomUserAgent(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getCustomUserAgent'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getCustomUserAgent', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -2868,17 +2983,17 @@ class WKUIDelegateHostApi { static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -3202,18 +3317,18 @@ class WKHttpCookieStoreHostApi { Future createFromWebsiteDataStore( int arg_identifier, int arg_websiteDataStoreIdentifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.createFromWebsiteDataStore'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.createFromWebsiteDataStore', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_websiteDataStoreIdentifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -3227,17 +3342,17 @@ class WKHttpCookieStoreHostApi { Future setCookie( int arg_identifier, NSHttpCookieData arg_cookie) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.setCookie'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.setCookie', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_cookie]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -3268,17 +3383,17 @@ class NSUrlHostApi { static const MessageCodec codec = StandardMessageCodec(); Future getAbsoluteString(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlHostApi.getAbsoluteString'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlHostApi.getAbsoluteString', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -3352,17 +3467,17 @@ class UIScrollViewDelegateHostApi { static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewDelegateHostApi.create'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewDelegateHostApi.create', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, @@ -3446,13 +3561,10 @@ class NSUrlCredentialHostApi { /// Create a new native instance and add it to the `InstanceManager`. Future createWithUser(int arg_identifier, String arg_user, String arg_password, NSUrlCredentialPersistence arg_persistence) async { - const String channelName = - 'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser'; final BasicMessageChannel channel = BasicMessageChannel( - channelName, - codec, - binaryMessenger: _binaryMessenger, - ); + 'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser', + codec, + binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, arg_user, @@ -3460,7 +3572,10 @@ class NSUrlCredentialHostApi { arg_persistence.index ]) as List?; if (replyList == null) { - throw _createConnectionError(channelName); + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart index 5c6d61aa8f7e..1dabbd0c24a5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart @@ -221,6 +221,21 @@ class NSErrorUserInfoKey { 'NSErrorFailingURLStringKey'; } +/// The metadata associated with the response to an HTTP protocol URL load +/// request. +/// +/// Wraps [NSHttpUrlResponse](https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc). +@immutable +class NSHttpUrlResponse { + /// Constructs an [NSHttpUrlResponse]. + const NSHttpUrlResponse({ + required this.statusCode, + }); + + /// The response’s HTTP status code. + final int statusCode; +} + /// Information about an error condition. /// /// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart index fb99be0940ef..11065ad0805f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart @@ -97,6 +97,21 @@ enum WKNavigationActionPolicy { cancel, } +/// Indicate whether to allow or cancel navigation to a webpage. +/// +/// Wraps [WKNavigationResponsePolicy](https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy?language=objc). +enum WKNavigationResponsePolicy { + /// Allow navigation to continue. + /// + /// See https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy/wknavigationresponsepolicyallow?language=objc. + allow, + + /// Cancel navigation. + /// + /// See https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy/wknavigationresponsepolicycancel?language=objc. + cancel, +} + /// Possible error values that WebKit APIs can return. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode. @@ -163,6 +178,24 @@ class WKNavigationAction { final WKNavigationType navigationType; } +/// An object that contains information about a response to a navigation request. +/// +/// Wraps [WKNavigationResponse](https://developer.apple.com/documentation/webkit/wknavigationresponse?language=objc). +@immutable +class WKNavigationResponse { + /// Constructs a [WKNavigationResponse]. + const WKNavigationResponse({ + required this.response, + required this.forMainFrame, + }); + + /// The URL request object associated with the navigation action. + final NSHttpUrlResponse response; + + /// The frame in which to display the new content. + final bool forMainFrame; +} + /// An object that contains information about a frame on a webpage. /// /// An instance of this class is a transient, data-only object; it does not @@ -858,6 +891,7 @@ class WKNavigationDelegate extends NSObject { this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, + this.decidePolicyForNavigationResponse, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, @@ -884,6 +918,7 @@ class WKNavigationDelegate extends NSObject { this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, + this.decidePolicyForNavigationResponse, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, @@ -918,6 +953,14 @@ class WKNavigationDelegate extends NSObject { WKNavigationAction navigationAction, )? decidePolicyForNavigationAction; + /// Called when permission is needed to navigate to new content. + /// + /// {@macro webview_flutter_wkwebview.foundation.callbacks} + final Future Function( + WKWebView webView, + WKNavigationResponse navigationResponse, + )? decidePolicyForNavigationResponse; + /// Called when an error occurred during navigation. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} @@ -950,6 +993,7 @@ class WKNavigationDelegate extends NSObject { didFinishNavigation: didFinishNavigation, didStartProvisionalNavigation: didStartProvisionalNavigation, decidePolicyForNavigationAction: decidePolicyForNavigationAction, + decidePolicyForNavigationResponse: decidePolicyForNavigationResponse, didFailNavigation: didFailNavigation, didFailProvisionalNavigation: didFailProvisionalNavigation, webViewWebContentProcessDidTerminate: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart index ac3bfc83f137..bdcc19eafd6b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart @@ -70,6 +70,14 @@ extension _WKNavigationActionPolicyConverter on WKNavigationActionPolicy { } } +extension _WKNavigationResponsePolicyConverter on WKNavigationResponsePolicy { + WKNavigationResponsePolicyEnum toWKNavigationResponsePolicyEnumData() { + return WKNavigationResponsePolicyEnum.values.firstWhere( + (WKNavigationResponsePolicyEnum element) => element.name == name, + ); + } +} + extension _NSHttpCookiePropertyKeyConverter on NSHttpCookiePropertyKey { NSHttpCookiePropertyKeyEnumData toNSHttpCookiePropertyKeyEnumData() { late final NSHttpCookiePropertyKeyEnum value; @@ -153,6 +161,13 @@ extension _NavigationActionDataConverter on WKNavigationActionData { } } +extension _NavigationResponseDataConverter on WKNavigationResponseData { + WKNavigationResponse toNavigationResponse() { + return WKNavigationResponse( + response: response.toNSUrlResponse(), forMainFrame: forMainFrame); + } +} + extension _WKFrameInfoDataConverter on WKFrameInfoData { WKFrameInfo toWKFrameInfo() { return WKFrameInfo( @@ -173,6 +188,12 @@ extension _NSUrlRequestDataConverter on NSUrlRequestData { } } +extension _NSUrlResponseDataConverter on NSHttpUrlResponseData { + NSHttpUrlResponse toNSUrlResponse() { + return NSHttpUrlResponse(statusCode: statusCode); + } +} + extension _WKNSErrorDataConverter on NSErrorData { NSError toNSError() { return NSError( @@ -907,6 +928,29 @@ class WKNavigationDelegateFlutterApiImpl ); } + @override + Future decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse, + ) async { + final Future Function( + WKWebView, + WKNavigationResponse navigationResponse, + )? function = _getDelegate(identifier).decidePolicyForNavigationResponse; + + if (function == null) { + return WKNavigationResponsePolicyEnum.allow; + } + + final WKNavigationResponsePolicy policy = await function( + instanceManager.getInstanceWithWeakReference(webViewIdentifier)! + as WKWebView, + navigationResponse.toNavigationResponse(), + ); + return policy.toWKNavigationResponsePolicyEnumData(); + } + @override void webViewWebContentProcessDidTerminate( int identifier, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart index e4793d7e0ed2..ffd9b845eab6 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart @@ -69,6 +69,10 @@ class WebKitProxy { WKWebView webView, WKNavigationAction navigationAction, )? decidePolicyForNavigationAction, + Future Function( + WKWebView webView, + WKNavigationResponse navigationResponse, + )? decidePolicyForNavigationResponse, void Function(WKWebView webView, NSError error)? didFailNavigation, void Function(WKWebView webView, NSError error)? didFailProvisionalNavigation, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart index c4e38230de86..b9e9386239e7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart @@ -986,6 +986,22 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { weakThis.target!._onPageStarted!(url ?? ''); } }, + decidePolicyForNavigationResponse: + (WKWebView webView, WKNavigationResponse response) async { + if (weakThis.target?._onHttpError != null && + response.response.statusCode >= 400) { + weakThis.target!._onHttpError!( + HttpResponseError( + response: WebResourceResponse( + uri: null, + statusCode: response.response.statusCode, + ), + ), + ); + } + + return WKNavigationResponsePolicy.allow; + }, decidePolicyForNavigationAction: ( WKWebView webView, WKNavigationAction action, @@ -1100,6 +1116,7 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { PageEventCallback? _onPageFinished; PageEventCallback? _onPageStarted; + HttpResponseErrorCallback? _onHttpError; ProgressCallback? _onProgress; WebResourceErrorCallback? _onWebResourceError; NavigationRequestCallback? _onNavigationRequest; @@ -1116,6 +1133,11 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { _onPageStarted = onPageStarted; } + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async { + _onHttpError = onHttpError; + } + @override Future setOnProgress(ProgressCallback onProgress) async { _onProgress = onProgress; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart index 5b0ea12db516..e660db3ef898 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -28,7 +28,7 @@ enum NSKeyValueObservingOptionsEnum { priorNotification, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class NSKeyValueObservingOptionsEnumData { late NSKeyValueObservingOptionsEnum value; @@ -44,7 +44,7 @@ enum NSKeyValueChangeEnum { replacement, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class NSKeyValueChangeEnumData { late NSKeyValueChangeEnum value; @@ -62,7 +62,7 @@ enum NSKeyValueChangeKeyEnum { unknown, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class NSKeyValueChangeKeyEnumData { late NSKeyValueChangeKeyEnum value; @@ -76,7 +76,7 @@ enum WKUserScriptInjectionTimeEnum { atDocumentEnd, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKUserScriptInjectionTimeEnumData { late WKUserScriptInjectionTimeEnum value; @@ -92,7 +92,7 @@ enum WKAudiovisualMediaTypeEnum { all, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKAudiovisualMediaTypeEnumData { late WKAudiovisualMediaTypeEnum value; @@ -112,7 +112,7 @@ enum WKWebsiteDataTypeEnum { indexedDBDatabases, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKWebsiteDataTypeEnumData { late WKWebsiteDataTypeEnum value; @@ -126,12 +126,20 @@ enum WKNavigationActionPolicyEnum { cancel, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKNavigationActionPolicyEnumData { late WKNavigationActionPolicyEnum value; } +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +enum WKNavigationResponsePolicyEnum { + allow, + cancel, +} + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -152,7 +160,7 @@ enum NSHttpCookiePropertyKeyEnum { version, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class NSHttpCookiePropertyKeyEnumData { late NSHttpCookiePropertyKeyEnum value; @@ -220,7 +228,7 @@ enum WKPermissionDecision { prompt, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKPermissionDecisionData { late WKPermissionDecision value; @@ -252,7 +260,7 @@ enum WKMediaCaptureType { unknown, } -// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// TODO(bparrishMines): Enums need be wrapped in a data class because they can't // be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 class WKMediaCaptureTypeData { late WKMediaCaptureType value; @@ -320,6 +328,13 @@ class NSUrlRequestData { late Map allHttpHeaderFields; } +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +class NSHttpUrlResponseData { + late int statusCode; +} + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -338,6 +353,14 @@ class WKNavigationActionData { late WKNavigationType navigationType; } +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +class WKNavigationResponseData { + late NSHttpUrlResponseData response; + late bool forMainFrame; +} + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -619,6 +642,16 @@ abstract class WKNavigationDelegateFlutterApi { WKNavigationActionData navigationAction, ); + @ObjCSelector( + 'decidePolicyForNavigationResponseForDelegateWithIdentifier:webViewIdentifier:navigationResponse:', + ) + @async + WKNavigationResponsePolicyEnum decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse, + ); + @ObjCSelector( 'didFailNavigationForDelegateWithIdentifier:webViewIdentifier:error:', ) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 18a0cef72e87..bffa7eaf66a5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.12.0 +version: 3.13.0 environment: sdk: ^3.2.3 @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter path: ^1.8.0 - webview_flutter_platform_interface: ^2.9.0 + webview_flutter_platform_interface: ^2.10.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart index fe13a87048e5..eb58b7eb11e2 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart @@ -63,7 +63,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i2.WKHttpCookieStore copy() => (super.noSuchMethod( Invocation.method( @@ -78,7 +77,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { ), ), ) as _i2.WKHttpCookieStore); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -97,7 +95,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { @@ -132,7 +129,6 @@ class MockWKWebsiteDataStore extends _i1.Mock Invocation.getter(#httpCookieStore), ), ) as _i2.WKHttpCookieStore); - @override _i3.Future removeDataOfTypes( Set<_i2.WKWebsiteDataType>? dataTypes, @@ -148,7 +144,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), returnValue: _i3.Future.value(false), ) as _i3.Future); - @override _i2.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( @@ -163,7 +158,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), ), ) as _i2.WKWebsiteDataStore); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -182,7 +176,6 @@ class MockWKWebsiteDataStore extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart index 8c421193d187..b5488a0e3931 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart @@ -2,6 +2,7 @@ // 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:math'; import 'package:flutter/material.dart'; @@ -35,44 +36,32 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewWidget', () { - late MockWKWebView mockWebView; - late MockWebViewWidgetProxy mockWebViewWidgetProxy; - late MockWKUserContentController mockUserContentController; - late MockWKPreferences mockPreferences; - late MockWKWebViewConfiguration mockWebViewConfiguration; - late MockWKUIDelegate mockUIDelegate; - late MockUIScrollView mockScrollView; - late MockWKWebsiteDataStore mockWebsiteDataStore; - late MockWKNavigationDelegate mockNavigationDelegate; - - late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; - late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; - - late WebKitWebViewPlatformController testController; - - setUp(() { - mockWebView = MockWKWebView(); - mockWebViewConfiguration = MockWKWebViewConfiguration(); - mockUserContentController = MockWKUserContentController(); - mockPreferences = MockWKPreferences(); - mockUIDelegate = MockWKUIDelegate(); - mockScrollView = MockUIScrollView(); - mockWebsiteDataStore = MockWKWebsiteDataStore(); - mockNavigationDelegate = MockWKNavigationDelegate(); - mockWebViewWidgetProxy = MockWebViewWidgetProxy(); + _WebViewMocks configureMocks() { + final _WebViewMocks mocks = _WebViewMocks( + webView: MockWKWebView(), + webViewWidgetProxy: MockWebViewWidgetProxy(), + userContentController: MockWKUserContentController(), + preferences: MockWKPreferences(), + webViewConfiguration: MockWKWebViewConfiguration(), + uiDelegate: MockWKUIDelegate(), + scrollView: MockUIScrollView(), + websiteDataStore: MockWKWebsiteDataStore(), + navigationDelegate: MockWKNavigationDelegate(), + callbacksHandler: MockWebViewPlatformCallbacksHandler(), + javascriptChannelRegistry: MockJavascriptChannelRegistry()); when( - mockWebViewWidgetProxy.createWebView( + mocks.webViewWidgetProxy.createWebView( any, observeValue: anyNamed('observeValue'), ), - ).thenReturn(mockWebView); + ).thenReturn(mocks.webView); when( - mockWebViewWidgetProxy.createUIDelgate( + mocks.webViewWidgetProxy.createUIDelgate( onCreateWebView: captureAnyNamed('onCreateWebView'), ), - ).thenReturn(mockUIDelegate); - when(mockWebViewWidgetProxy.createNavigationDelegate( + ).thenReturn(mocks.uiDelegate); + when(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -82,30 +71,33 @@ void main() { didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), - )).thenReturn(mockNavigationDelegate); - when(mockWebView.configuration).thenReturn(mockWebViewConfiguration); - when(mockWebViewConfiguration.userContentController).thenReturn( - mockUserContentController, + )).thenReturn(mocks.navigationDelegate); + when(mocks.webView.configuration).thenReturn(mocks.webViewConfiguration); + when(mocks.webViewConfiguration.userContentController).thenReturn( + mocks.userContentController, ); - when(mockWebViewConfiguration.preferences).thenReturn(mockPreferences); + when(mocks.webViewConfiguration.preferences) + .thenReturn(mocks.preferences); - when(mockWebView.scrollView).thenReturn(mockScrollView); + when(mocks.webView.scrollView).thenReturn(mocks.scrollView); - when(mockWebViewConfiguration.websiteDataStore).thenReturn( - mockWebsiteDataStore, + when(mocks.webViewConfiguration.websiteDataStore).thenReturn( + mocks.websiteDataStore, ); + return mocks; + } - mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); - mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); - }); - - // Builds a WebViewCupertinoWidget with default parameters. - Future buildWidget( - WidgetTester tester, { + // Builds a WebViewCupertinoWidget with default parameters and returns its + // controller. + Future buildWidget( + WidgetTester tester, + _WebViewMocks mocks, { CreationParams? creationParams, bool hasNavigationDelegate = false, bool hasProgressTracking = false, }) async { + final Completer testController = + Completer(); await tester.pumpWidget(WebKitWebViewWidget( creationParams: creationParams ?? CreationParams( @@ -114,28 +106,31 @@ void main() { hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, )), - callbacksHandler: mockCallbacksHandler, - javascriptChannelRegistry: mockJavascriptChannelRegistry, - webViewProxy: mockWebViewWidgetProxy, - configuration: mockWebViewConfiguration, + callbacksHandler: mocks.callbacksHandler, + javascriptChannelRegistry: mocks.javascriptChannelRegistry, + webViewProxy: mocks.webViewWidgetProxy, + configuration: mocks.webViewConfiguration, onBuildWidget: (WebKitWebViewPlatformController controller) { - testController = controller; + testController.complete(controller); return Container(); }, )); await tester.pumpAndSettle(); + return testController.future; } - testWidgets('build $WebKitWebViewWidget', (WidgetTester tester) async { - await buildWidget(tester); + testWidgets('build WebKitWebViewWidget', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); }); testWidgets('Requests to open a new window loads request in same window', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView, WKWebViewConfiguration, WKNavigationAction) - onCreateWebView = verify(mockWebViewWidgetProxy.createUIDelgate( + onCreateWebView = verify(mocks.webViewWidgetProxy.createUIDelgate( onCreateWebView: captureAnyNamed('onCreateWebView'))) .captured .single @@ -144,8 +139,8 @@ void main() { const NSUrlRequest request = NSUrlRequest(url: 'https://google.com'); onCreateWebView( - mockWebView, - mockWebViewConfiguration, + mocks.webView, + mocks.webViewConfiguration, const WKNavigationAction( request: request, targetFrame: WKFrameInfo(isMainFrame: false, request: request), @@ -153,13 +148,15 @@ void main() { ), ); - verify(mockWebView.loadRequest(request)); + verify(mocks.webView.loadRequest(request)); }); group('CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( initialUrl: 'https://www.google.com', webSettings: WebSettings( @@ -168,15 +165,17 @@ void main() { ), ), ); - final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) - .captured - .single as NSUrlRequest; + final NSUrlRequest request = + verify(mocks.webView.loadRequest(captureAny)).captured.single + as NSUrlRequest; expect(request.url, 'https://www.google.com'); }); testWidgets('backgroundColor', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( backgroundColor: Colors.red, webSettings: WebSettings( @@ -186,14 +185,16 @@ void main() { ), ); - verify(mockWebView.setOpaque(false)); - verify(mockWebView.setBackgroundColor(Colors.transparent)); - verify(mockScrollView.setBackgroundColor(Colors.red)); + verify(mocks.webView.setOpaque(false)); + verify(mocks.webView.setBackgroundColor(Colors.transparent)); + verify(mocks.scrollView.setBackgroundColor(Colors.red)); }); testWidgets('userAgent', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( userAgent: 'MyUserAgent', webSettings: WebSettings( @@ -203,12 +204,14 @@ void main() { ), ); - verify(mockWebView.setCustomUserAgent('MyUserAgent')); + verify(mocks.webView.setCustomUserAgent('MyUserAgent')); }); testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -217,15 +220,17 @@ void main() { ), ); - verify(mockWebViewConfiguration + verify(mocks.webViewConfiguration .setMediaTypesRequiringUserActionForPlayback({ WKAudiovisualMediaType.all, })); }); testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, webSettings: WebSettings( @@ -235,15 +240,16 @@ void main() { ), ); - verify(mockWebViewConfiguration + verify(mocks.webViewConfiguration .setMediaTypesRequiringUserActionForPlayback({ WKAudiovisualMediaType.none, })); }); testWidgets('javascriptChannelNames', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( @@ -252,6 +258,7 @@ void main() { await buildWidget( tester, + mocks, creationParams: CreationParams( javascriptChannelNames: {'a', 'b'}, webSettings: WebSettings( @@ -262,7 +269,7 @@ void main() { ); final List javaScriptChannels = verify( - mockUserContentController.addScriptMessageHandler( + mocks.userContentController.addScriptMessageHandler( captureAny, captureAny, ), @@ -281,8 +288,10 @@ void main() { group('WebSettings', () { testWidgets('javascriptMode', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -292,12 +301,14 @@ void main() { ), ); - verify(mockPreferences.setJavaScriptEnabled(true)); + verify(mocks.preferences.setJavaScriptEnabled(true)); }); testWidgets('userAgent', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.of('myUserAgent'), @@ -306,22 +317,25 @@ void main() { ), ); - verify(mockWebView.setCustomUserAgent('myUserAgent')); + verify(mocks.webView.setCustomUserAgent('myUserAgent')); }); testWidgets( 'enabling zoom re-adds JavaScript channels', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); - await buildWidget( + final WebKitWebViewPlatformController testController = + await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -332,7 +346,7 @@ void main() { ), ); - clearInteractions(mockUserContentController); + clearInteractions(mocks.userContentController); await testController.updateSettings(WebSettings( userAgent: const WebSetting.absent(), @@ -340,9 +354,10 @@ void main() { )); final List javaScriptChannels = verifyInOrder([ - mockUserContentController.removeAllUserScripts(), - mockUserContentController.removeScriptMessageHandler('myChannel'), - mockUserContentController.addScriptMessageHandler( + mocks.userContentController.removeAllUserScripts(), + mocks.userContentController + .removeScriptMessageHandler('myChannel'), + mocks.userContentController.addScriptMessageHandler( captureAny, captureAny, ), @@ -359,8 +374,11 @@ void main() { testWidgets( 'enabling zoom removes script', (WidgetTester tester) async { - await buildWidget( + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -370,15 +388,15 @@ void main() { ), ); - clearInteractions(mockUserContentController); + clearInteractions(mocks.userContentController); await testController.updateSettings(WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: true, )); - verify(mockUserContentController.removeAllUserScripts()); - verifyNever(mockUserContentController.addScriptMessageHandler( + verify(mocks.userContentController.removeAllUserScripts()); + verifyNever(mocks.userContentController.addScriptMessageHandler( any, any, )); @@ -386,8 +404,10 @@ void main() { ); testWidgets('zoomEnabled is false', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -398,7 +418,7 @@ void main() { ); final WKUserScript zoomScript = - verify(mockUserContentController.addUserScript(captureAny)) + verify(mocks.userContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); @@ -415,8 +435,10 @@ void main() { }); testWidgets('allowsInlineMediaPlayback', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -425,52 +447,60 @@ void main() { ), ); - verify(mockWebViewConfiguration.setAllowsInlineMediaPlayback(true)); + verify(mocks.webViewConfiguration.setAllowsInlineMediaPlayback(true)); }); }); }); group('WebKitWebViewPlatformController', () { testWidgets('loadFile', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadFile('/path/to/file.html'); - verify(mockWebView.loadFileUrl( + verify(mocks.webView.loadFileUrl( '/path/to/file.html', readAccessUrl: '/path/to', )); }); testWidgets('loadFlutterAsset', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadFlutterAsset('test_assets/index.html'); - verify(mockWebView.loadFlutterAsset('test_assets/index.html')); + verify(mocks.webView.loadFlutterAsset('test_assets/index.html')); }); testWidgets('loadHtmlString', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); const String htmlString = 'Test data.'; await testController.loadHtmlString(htmlString, baseUrl: 'baseUrl'); - verify(mockWebView.loadHtmlString( + verify(mocks.webView.loadHtmlString( 'Test data.', baseUrl: 'baseUrl', )); }); testWidgets('loadUrl', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadUrl( 'https://www.google.com', {'a': 'header'}, ); - final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) - .captured - .single as NSUrlRequest; + final NSUrlRequest request = + verify(mocks.webView.loadRequest(captureAny)).captured.single + as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {'a': 'header'}); }); @@ -478,7 +508,9 @@ void main() { group('loadRequest', () { testWidgets('Throws ArgumentError for empty scheme', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); expect( () async => testController.loadRequest( @@ -491,7 +523,9 @@ void main() { }); testWidgets('GET without headers', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), @@ -499,7 +533,7 @@ void main() { )); final NSUrlRequest request = - verify(mockWebView.loadRequest(captureAny)).captured.single + verify(mocks.webView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {}); @@ -507,7 +541,9 @@ void main() { }); testWidgets('GET with headers', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), @@ -516,7 +552,7 @@ void main() { )); final NSUrlRequest request = - verify(mockWebView.loadRequest(captureAny)).captured.single + verify(mocks.webView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {'a': 'header'}); @@ -524,7 +560,9 @@ void main() { }); testWidgets('POST without body', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), @@ -532,14 +570,16 @@ void main() { )); final NSUrlRequest request = - verify(mockWebView.loadRequest(captureAny)).captured.single + verify(mocks.webView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); }); testWidgets('POST with body', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), @@ -547,7 +587,7 @@ void main() { body: Uint8List.fromList('Test Body'.codeUnits))); final NSUrlRequest request = - verify(mockWebView.loadRequest(captureAny)).captured.single + verify(mocks.webView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); @@ -559,48 +599,60 @@ void main() { }); testWidgets('canGoBack', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.canGoBack()).thenAnswer( + when(mocks.webView.canGoBack()).thenAnswer( (_) => Future.value(false), ); expect(testController.canGoBack(), completion(false)); }); testWidgets('canGoForward', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.canGoForward()).thenAnswer( + when(mocks.webView.canGoForward()).thenAnswer( (_) => Future.value(true), ); expect(testController.canGoForward(), completion(true)); }); testWidgets('goBack', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.goBack(); - verify(mockWebView.goBack()); + verify(mocks.webView.goBack()); }); testWidgets('goForward', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.goForward(); - verify(mockWebView.goForward()); + verify(mocks.webView.goForward()); }); testWidgets('reload', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.reload(); - verify(mockWebView.reload()); + verify(mocks.webView.reload()); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( @@ -611,9 +663,11 @@ void main() { testWidgets('evaluateJavascript with null return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(), ); // The legacy implementation of webview_flutter_wkwebview would convert @@ -627,9 +681,11 @@ void main() { testWidgets('evaluateJavascript with bool return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(true), ); // The legacy implementation of webview_flutter_wkwebview would convert @@ -644,9 +700,11 @@ void main() { testWidgets('evaluateJavascript with double return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(1.0), ); // The legacy implementation of webview_flutter_wkwebview would convert @@ -663,9 +721,11 @@ void main() { testWidgets('evaluateJavascript with list return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value([1, 'string', null]), ); // The legacy implementation of webview_flutter_wkwebview would convert @@ -679,9 +739,11 @@ void main() { testWidgets('evaluateJavascript with map return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value({ 1: 'string', null: null, @@ -698,9 +760,11 @@ void main() { testWidgets('evaluateJavascript throws exception', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')) + when(mocks.webView.evaluateJavaScript('runJavaScript')) .thenThrow(Error()); expect( testController.evaluateJavascript('runJavaScript'), @@ -709,9 +773,11 @@ void main() { }); testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( @@ -723,9 +789,11 @@ void main() { testWidgets( 'runJavascriptReturningResult throws error on null return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(), ); expect( @@ -736,9 +804,11 @@ void main() { testWidgets('runJavascriptReturningResult with bool return value', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(false), ); // The legacy implementation of webview_flutter_wkwebview would convert @@ -752,9 +822,11 @@ void main() { }); testWidgets('runJavascript', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + when(mocks.webView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( @@ -766,9 +838,11 @@ void main() { testWidgets( 'runJavascript ignores exception with unsupported javascript type', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.evaluateJavaScript('runJavaScript')) + when(mocks.webView.evaluateJavaScript('runJavaScript')) .thenThrow(PlatformException( code: '', details: const NSError( @@ -783,57 +857,70 @@ void main() { }); testWidgets('getTitle', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.getTitle()) + when(mocks.webView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); expect(testController.getTitle(), completion('Web Title')); }); testWidgets('currentUrl', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockWebView.getUrl()) + when(mocks.webView.getUrl()) .thenAnswer((_) => Future.value('myUrl.com')); expect(testController.currentUrl(), completion('myUrl.com')); }); testWidgets('scrollTo', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.scrollTo(2, 4); - verify(mockScrollView.setContentOffset(const Point(2.0, 4.0))); + verify( + mocks.scrollView.setContentOffset(const Point(2.0, 4.0))); }); testWidgets('scrollBy', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.scrollBy(2, 4); - verify(mockScrollView.scrollBy(const Point(2.0, 4.0))); + verify(mocks.scrollView.scrollBy(const Point(2.0, 4.0))); }); testWidgets('getScrollX', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockScrollView.getContentOffset()).thenAnswer( + when(mocks.scrollView.getContentOffset()).thenAnswer( (_) => Future>.value(const Point(8.0, 16.0))); expect(testController.getScrollX(), completion(8.0)); }); testWidgets('getScrollY', (WidgetTester tester) async { - await buildWidget(tester); - - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); - when(mockScrollView.getContentOffset()).thenAnswer( + when(mocks.scrollView.getContentOffset()).thenAnswer( (_) => Future>.value(const Point(8.0, 16.0))); expect(testController.getScrollY(), completion(16.0)); }); testWidgets('clearCache', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); when( - mockWebsiteDataStore.removeDataOfTypes( + mocks.websiteDataStore.removeDataOfTypes( { WKWebsiteDataType.memoryCache, WKWebsiteDataType.diskCache, @@ -848,20 +935,22 @@ void main() { }); testWidgets('addJavascriptChannels', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); - await buildWidget(tester); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.addJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify( - mockUserContentController.addScriptMessageHandler( - captureAny, captureAny), + mocks.userContentController + .addScriptMessageHandler(captureAny, captureAny), ).captured; expect( javaScriptChannels[0], @@ -875,7 +964,7 @@ void main() { expect(javaScriptChannels[3], 'd'); final List userScripts = - verify(mockUserContentController.addUserScript(captureAny)) + verify(mocks.userContentController.addUserScript(captureAny)) .captured .cast(); expect(userScripts[0].source, 'window.c = webkit.messageHandlers.c;'); @@ -893,27 +982,29 @@ void main() { }); testWidgets('removeJavascriptChannels', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); - await buildWidget(tester); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.addJavascriptChannels({'c', 'd'}); - reset(mockUserContentController); + reset(mocks.userContentController); await testController.removeJavascriptChannels({'c'}); - verify(mockUserContentController.removeAllUserScripts()); - verify(mockUserContentController.removeScriptMessageHandler('c')); - verify(mockUserContentController.removeScriptMessageHandler('d')); + verify(mocks.userContentController.removeAllUserScripts()); + verify(mocks.userContentController.removeScriptMessageHandler('c')); + verify(mocks.userContentController.removeScriptMessageHandler('d')); final List javaScriptChannels = verify( - mockUserContentController.addScriptMessageHandler( + mocks.userContentController.addScriptMessageHandler( captureAny, captureAny, ), @@ -925,7 +1016,7 @@ void main() { expect(javaScriptChannels[1], 'd'); final List userScripts = - verify(mockUserContentController.addUserScript(captureAny)) + verify(mocks.userContentController.addUserScript(captureAny)) .captured .cast(); expect(userScripts[0].source, 'window.d = webkit.messageHandlers.d;'); @@ -938,16 +1029,19 @@ void main() { testWidgets('removeJavascriptChannels with zoom disabled', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); - await buildWidget( + final WebKitWebViewPlatformController testController = + await buildWidget( tester, + mocks, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), @@ -958,11 +1052,11 @@ void main() { ); await testController.addJavascriptChannels({'c'}); - clearInteractions(mockUserContentController); + clearInteractions(mocks.userContentController); await testController.removeJavascriptChannels({'c'}); final WKUserScript zoomScript = - verify(mockUserContentController.addUserScript(captureAny)) + verify(mocks.userContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); @@ -981,10 +1075,11 @@ void main() { group('WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView, String) didStartProvisionalNavigation = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: captureAnyNamed('didStartProvisionalNavigation'), @@ -996,16 +1091,17 @@ void main() { webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, String); - didStartProvisionalNavigation(mockWebView, 'https://google.com'); + didStartProvisionalNavigation(mocks.webView, 'https://google.com'); - verify(mockCallbacksHandler.onPageStarted('https://google.com')); + verify(mocks.callbacksHandler.onPageStarted('https://google.com')); }); testWidgets('onPageFinished', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView, String) didFinishNavigation = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: captureAnyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -1017,17 +1113,18 @@ void main() { webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, String); - didFinishNavigation(mockWebView, 'https://google.com'); + didFinishNavigation(mocks.webView, 'https://google.com'); - verify(mockCallbacksHandler.onPageFinished('https://google.com')); + verify(mocks.callbacksHandler.onPageFinished('https://google.com')); }); testWidgets('onWebResourceError from didFailNavigation', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView, NSError) didFailNavigation = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -1041,7 +1138,7 @@ void main() { )).captured.single as void Function(WKWebView, NSError); didFailNavigation( - mockWebView, + mocks.webView, const NSError( code: WKErrorCode.webViewInvalidated, domain: 'domain', @@ -1052,7 +1149,7 @@ void main() { ); final WebResourceError error = - verify(mockCallbacksHandler.onWebResourceError(captureAny)) + verify(mocks.callbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'my desc'); @@ -1063,10 +1160,11 @@ void main() { testWidgets('onWebResourceError from didFailProvisionalNavigation', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView, NSError) didFailProvisionalNavigation = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -1080,7 +1178,7 @@ void main() { )).captured.single as void Function(WKWebView, NSError); didFailProvisionalNavigation( - mockWebView, + mocks.webView, const NSError( code: WKErrorCode.webContentProcessTerminated, domain: 'domain', @@ -1091,7 +1189,7 @@ void main() { ); final WebResourceError error = - verify(mockCallbacksHandler.onWebResourceError(captureAny)) + verify(mocks.callbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'my desc'); @@ -1106,10 +1204,11 @@ void main() { testWidgets( 'onWebResourceError from webViewWebContentProcessDidTerminate', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); final void Function(WKWebView) webViewWebContentProcessDidTerminate = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -1121,10 +1220,10 @@ void main() { webViewWebContentProcessDidTerminate: captureAnyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView); - webViewWebContentProcessDidTerminate(mockWebView); + webViewWebContentProcessDidTerminate(mocks.webView); final WebResourceError error = - verify(mockCallbacksHandler.onWebResourceError(captureAny)) + verify(mocks.callbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, ''); @@ -1138,11 +1237,12 @@ void main() { testWidgets('onNavigationRequest from decidePolicyForNavigationAction', (WidgetTester tester) async { - await buildWidget(tester, hasNavigationDelegate: true); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks, hasNavigationDelegate: true); final Future Function( WKWebView, WKNavigationAction) decidePolicyForNavigationAction = - verify(mockWebViewWidgetProxy.createNavigationDelegate( + verify(mocks.webViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), @@ -1156,14 +1256,14 @@ void main() { )).captured.single as Future Function( WKWebView, WKNavigationAction); - when(mockCallbacksHandler.onNavigationRequest( + when(mocks.callbacksHandler.onNavigationRequest( isForMainFrame: argThat(isFalse, named: 'isForMainFrame'), url: 'https://google.com', )).thenReturn(true); expect( decidePolicyForNavigationAction( - mockWebView, + mocks.webView, const WKNavigationAction( request: NSUrlRequest(url: 'https://google.com'), targetFrame: WKFrameInfo( @@ -1175,17 +1275,18 @@ void main() { completion(WKNavigationActionPolicy.allow), ); - verify(mockCallbacksHandler.onNavigationRequest( + verify(mocks.callbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: false, )); }); testWidgets('onProgress', (WidgetTester tester) async { - await buildWidget(tester, hasProgressTracking: true); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks, hasProgressTracking: true); - verify(mockWebView.addObserver( - mockWebView, + verify(mocks.webView.addObserver( + mocks.webView, keyPath: 'estimatedProgress', options: { NSKeyValueObservingOptions.newValue, @@ -1193,7 +1294,7 @@ void main() { )); final void Function(String, NSObject, Map) - observeValue = verify(mockWebViewWidgetProxy.createWebView(any, + observeValue = verify(mocks.webViewWidgetProxy.createWebView(any, observeValue: captureAnyNamed('observeValue'))) .captured .single @@ -1202,19 +1303,20 @@ void main() { observeValue( 'estimatedProgress', - mockWebView, + mocks.webView, {NSKeyValueChangeKey.newValue: 0.32}, ); - verify(mockCallbacksHandler.onProgress(32)); + verify(mocks.callbacksHandler.onProgress(32)); }); testWidgets('progress observer is not removed without being set first', (WidgetTester tester) async { - await buildWidget(tester); + final _WebViewMocks mocks = configureMocks(); + await buildWidget(tester, mocks); - verifyNever(mockWebView.removeObserver( - mockWebView, + verifyNever(mocks.webView.removeObserver( + mocks.webView, keyPath: 'estimatedProgress', )); }); @@ -1222,20 +1324,22 @@ void main() { group('JavascriptChannelRegistry', () { testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { + final _WebViewMocks mocks = configureMocks(); when( - mockWebViewWidgetProxy.createScriptMessageHandler( + mocks.webViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); - await buildWidget(tester); + final WebKitWebViewPlatformController testController = + await buildWidget(tester, mocks); await testController.addJavascriptChannels({'hello'}); final void Function(WKUserContentController, WKScriptMessage) - didReceiveScriptMessage = verify( - mockWebViewWidgetProxy.createScriptMessageHandler( + didReceiveScriptMessage = verify(mocks.webViewWidgetProxy + .createScriptMessageHandler( didReceiveScriptMessage: captureAnyNamed('didReceiveScriptMessage'))) .captured @@ -1243,10 +1347,10 @@ void main() { as void Function(WKUserContentController, WKScriptMessage); didReceiveScriptMessage( - mockUserContentController, + mocks.userContentController, const WKScriptMessage(name: 'hello', body: 'A message.'), ); - verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage( + verify(mocks.javascriptChannelRegistry.onJavascriptChannelMessage( 'hello', 'A message.', )); @@ -1254,3 +1358,32 @@ void main() { }); }); } + +/// A collection of mocks used in constructing a WebViewWidget. +class _WebViewMocks { + _WebViewMocks({ + required this.webView, + required this.webViewWidgetProxy, + required this.userContentController, + required this.preferences, + required this.webViewConfiguration, + required this.uiDelegate, + required this.scrollView, + required this.websiteDataStore, + required this.navigationDelegate, + required this.callbacksHandler, + required this.javascriptChannelRegistry, + }); + + final MockWKWebView webView; + final MockWebViewWidgetProxy webViewWidgetProxy; + final MockWKUserContentController userContentController; + final MockWKPreferences preferences; + final MockWKWebViewConfiguration webViewConfiguration; + final MockWKUIDelegate uiDelegate; + final MockUIScrollView scrollView; + final MockWKWebsiteDataStore websiteDataStore; + final MockWKNavigationDelegate navigationDelegate; + final MockWebViewPlatformCallbacksHandler callbacksHandler; + final MockJavascriptChannelRegistry javascriptChannelRegistry; +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart index 411002e479ee..a33d5ac58302 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart @@ -174,7 +174,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { ), )), ) as _i5.Future<_i2.Point>); - @override _i5.Future scrollBy(_i2.Point? offset) => (super.noSuchMethod( Invocation.method( @@ -184,7 +183,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setContentOffset(_i2.Point? offset) => (super.noSuchMethod( @@ -195,7 +193,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setDelegate(_i3.UIScrollViewDelegate? delegate) => (super.noSuchMethod( @@ -206,7 +203,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i3.UIScrollView copy() => (super.noSuchMethod( Invocation.method( @@ -221,7 +217,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { ), ), ) as _i3.UIScrollView); - @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( @@ -231,7 +226,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( @@ -241,7 +235,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -260,7 +253,6 @@ class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -301,7 +293,6 @@ class MockWKNavigationDelegate extends _i1.Mock ), ), ) as _i4.WKNavigationDelegate); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -320,7 +311,6 @@ class MockWKNavigationDelegate extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -355,7 +345,6 @@ class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i4.WKPreferences copy() => (super.noSuchMethod( Invocation.method( @@ -370,7 +359,6 @@ class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { ), ), ) as _i4.WKPreferences); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -389,7 +377,6 @@ class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -430,7 +417,6 @@ class MockWKScriptMessageHandler extends _i1.Mock _i4.WKUserContentController, _i4.WKScriptMessage, )); - @override _i4.WKScriptMessageHandler copy() => (super.noSuchMethod( Invocation.method( @@ -445,7 +431,6 @@ class MockWKScriptMessageHandler extends _i1.Mock ), ), ) as _i4.WKScriptMessageHandler); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -464,7 +449,6 @@ class MockWKScriptMessageHandler extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -498,7 +482,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { Invocation.getter(#configuration), ), ) as _i4.WKWebViewConfiguration); - @override _i3.UIScrollView get scrollView => (super.noSuchMethod( Invocation.getter(#scrollView), @@ -507,7 +490,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { Invocation.getter(#scrollView), ), ) as _i3.UIScrollView); - @override _i5.Future setUIDelegate(_i4.WKUIDelegate? delegate) => (super.noSuchMethod( @@ -518,7 +500,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => (super.noSuchMethod( @@ -529,7 +510,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future getUrl() => (super.noSuchMethod( Invocation.method( @@ -538,7 +518,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future getEstimatedProgress() => (super.noSuchMethod( Invocation.method( @@ -547,7 +526,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(0.0), ) as _i5.Future); - @override _i5.Future loadRequest(_i7.NSUrlRequest? request) => (super.noSuchMethod( @@ -558,7 +536,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future loadHtmlString( String? string, { @@ -573,7 +550,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future loadFileUrl( String? url, { @@ -588,7 +564,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( @@ -598,7 +573,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( @@ -607,7 +581,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(false), ) as _i5.Future); - @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( @@ -616,7 +589,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(false), ) as _i5.Future); - @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( @@ -626,7 +598,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( @@ -636,7 +607,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( @@ -646,7 +616,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( @@ -655,7 +624,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( @@ -666,7 +634,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( @@ -676,7 +643,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future evaluateJavaScript(String? javaScriptString) => (super.noSuchMethod( @@ -686,7 +652,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setInspectable(bool? inspectable) => (super.noSuchMethod( Invocation.method( @@ -696,7 +661,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future getCustomUserAgent() => (super.noSuchMethod( Invocation.method( @@ -705,7 +669,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), returnValue: _i5.Future.value(), ) as _i5.Future); - @override _i4.WKWebView copy() => (super.noSuchMethod( Invocation.method( @@ -720,7 +683,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { ), ), ) as _i4.WKWebView); - @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( @@ -730,7 +692,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( @@ -740,7 +701,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -759,7 +719,6 @@ class MockWKWebView extends _i1.Mock implements _i4.WKWebView { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -794,7 +753,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#userContentController), ), ) as _i4.WKUserContentController); - @override _i4.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), @@ -803,7 +761,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#preferences), ), ) as _i4.WKPreferences); - @override _i4.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), @@ -812,7 +769,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#websiteDataStore), ), ) as _i4.WKWebsiteDataStore); - @override _i5.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( @@ -823,7 +779,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setLimitsNavigationsToAppBoundDomains(bool? limit) => (super.noSuchMethod( @@ -834,7 +789,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future setMediaTypesRequiringUserActionForPlayback( Set<_i4.WKAudiovisualMediaType>? types) => @@ -846,7 +800,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i4.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( @@ -861,7 +814,6 @@ class MockWKWebViewConfiguration extends _i1.Mock ), ), ) as _i4.WKWebViewConfiguration); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -880,7 +832,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -915,7 +866,6 @@ class MockWKWebsiteDataStore extends _i1.Mock Invocation.getter(#httpCookieStore), ), ) as _i4.WKHttpCookieStore); - @override _i5.Future removeDataOfTypes( Set<_i4.WKWebsiteDataType>? dataTypes, @@ -931,7 +881,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), returnValue: _i5.Future.value(false), ) as _i5.Future); - @override _i4.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( @@ -946,7 +895,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), ), ) as _i4.WKWebsiteDataStore); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -965,7 +913,6 @@ class MockWKWebsiteDataStore extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -1005,7 +952,6 @@ class MockWKUIDelegate extends _i1.Mock implements _i4.WKUIDelegate { ), ), ) as _i4.WKUIDelegate); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -1024,7 +970,6 @@ class MockWKUIDelegate extends _i1.Mock implements _i4.WKUIDelegate { returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -1067,7 +1012,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeScriptMessageHandler(String? name) => (super.noSuchMethod( @@ -1078,7 +1022,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( Invocation.method( @@ -1088,7 +1031,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future addUserScript(_i4.WKUserScript? userScript) => (super.noSuchMethod( @@ -1099,7 +1041,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeAllUserScripts() => (super.noSuchMethod( Invocation.method( @@ -1109,7 +1050,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i4.WKUserContentController copy() => (super.noSuchMethod( Invocation.method( @@ -1124,7 +1064,6 @@ class MockWKUserContentController extends _i1.Mock ), ), ) as _i4.WKUserContentController); - @override _i5.Future addObserver( _i7.NSObject? observer, { @@ -1143,7 +1082,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); - @override _i5.Future removeObserver( _i7.NSObject? observer, { @@ -1174,7 +1112,6 @@ class MockJavascriptChannelRegistry extends _i1.Mock Invocation.getter(#channels), returnValue: {}, ) as Map); - @override void onJavascriptChannelMessage( String? channel, @@ -1190,7 +1127,6 @@ class MockJavascriptChannelRegistry extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void updateJavascriptChannelsFromSet(Set<_i9.JavascriptChannel>? channels) => super.noSuchMethod( @@ -1227,7 +1163,6 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock ), returnValue: _i5.Future.value(false), ) as _i5.FutureOr); - @override void onPageStarted(String? url) => super.noSuchMethod( Invocation.method( @@ -1236,7 +1171,6 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void onPageFinished(String? url) => super.noSuchMethod( Invocation.method( @@ -1245,7 +1179,6 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void onProgress(int? progress) => super.noSuchMethod( Invocation.method( @@ -1254,7 +1187,6 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void onWebResourceError(_i10.WebResourceError? error) => super.noSuchMethod( Invocation.method( @@ -1298,7 +1230,6 @@ class MockWebViewWidgetProxy extends _i1.Mock ), ), ) as _i4.WKWebView); - @override _i4.WKScriptMessageHandler createScriptMessageHandler( {required void Function( @@ -1320,7 +1251,6 @@ class MockWebViewWidgetProxy extends _i1.Mock ), ), ) as _i4.WKScriptMessageHandler); - @override _i4.WKUIDelegate createUIDelgate( {void Function( @@ -1343,7 +1273,6 @@ class MockWebViewWidgetProxy extends _i1.Mock ), ), ) as _i4.WKUIDelegate); - @override _i4.WKNavigationDelegate createNavigationDelegate({ void Function( diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart index 82f754af224e..982b50810e6e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.1.2), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), 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, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -1245,51 +1245,57 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec { } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueChangeKeyEnumData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueObservingOptionsEnumData) { + } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is ObjectOrIdentifier) { + } else if (value is NSUrlRequestData) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is WKAudiovisualMediaTypeEnumData) { + } else if (value is ObjectOrIdentifier) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is WKMediaCaptureTypeData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKMediaCaptureTypeData) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is WKPermissionDecisionData) { + } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is WKScriptMessageData) { + } else if (value is WKNavigationResponseData) { buffer.putUint8(142); writeValue(buffer, value.encode()); - } else if (value is WKSecurityOriginData) { + } else if (value is WKPermissionDecisionData) { buffer.putUint8(143); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptData) { + } else if (value is WKScriptMessageData) { buffer.putUint8(144); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptInjectionTimeEnumData) { + } else if (value is WKSecurityOriginData) { buffer.putUint8(145); writeValue(buffer, value.encode()); - } else if (value is WKWebsiteDataTypeEnumData) { + } else if (value is WKUserScriptData) { buffer.putUint8(146); writeValue(buffer, value.encode()); + } else if (value is WKUserScriptInjectionTimeEnumData) { + buffer.putUint8(147); + writeValue(buffer, value.encode()); + } else if (value is WKWebsiteDataTypeEnumData) { + buffer.putUint8(148); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1307,34 +1313,38 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec { case 131: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 132: - return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 133: - return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); + return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 134: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 135: - return ObjectOrIdentifier.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 136: - return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); + return ObjectOrIdentifier.decode(readValue(buffer)!); case 137: - return WKFrameInfoData.decode(readValue(buffer)!); + return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 138: - return WKMediaCaptureTypeData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 139: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKMediaCaptureTypeData.decode(readValue(buffer)!); case 140: - return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + return WKNavigationActionData.decode(readValue(buffer)!); case 141: - return WKPermissionDecisionData.decode(readValue(buffer)!); + return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 142: - return WKScriptMessageData.decode(readValue(buffer)!); + return WKNavigationResponseData.decode(readValue(buffer)!); case 143: - return WKSecurityOriginData.decode(readValue(buffer)!); + return WKPermissionDecisionData.decode(readValue(buffer)!); case 144: - return WKUserScriptData.decode(readValue(buffer)!); + return WKScriptMessageData.decode(readValue(buffer)!); case 145: - return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + return WKSecurityOriginData.decode(readValue(buffer)!); case 146: + return WKUserScriptData.decode(readValue(buffer)!); + case 147: + return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + case 148: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart index df4e12e6082f..a34a190ac497 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart @@ -38,7 +38,6 @@ class MockTestNSObjectHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addObserver( int? identifier, @@ -58,7 +57,6 @@ class MockTestNSObjectHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removeObserver( int? identifier, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart index 66bbc7275e69..54e91efb6a66 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart @@ -40,7 +40,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void createFromWebView( int? identifier, @@ -56,7 +55,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setAllowsInlineMediaPlayback( int? identifier, @@ -72,7 +70,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setLimitsNavigationsToAppBoundDomains( int? identifier, @@ -88,7 +85,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setMediaTypesRequiringUserActionForPlayback( int? identifier, @@ -130,7 +126,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setUIDelegate( int? identifier, @@ -146,7 +141,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setNavigationDelegate( int? identifier, @@ -162,13 +156,11 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getUrl(int? identifier) => (super.noSuchMethod(Invocation.method( #getUrl, [identifier], )) as String?); - @override double getEstimatedProgress(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -177,7 +169,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: 0.0, ) as double); - @override void loadRequest( int? identifier, @@ -193,7 +184,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadHtmlString( int? identifier, @@ -211,7 +201,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadFileUrl( int? identifier, @@ -229,7 +218,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadFlutterAsset( int? identifier, @@ -245,7 +233,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override bool canGoBack(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -254,7 +241,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: false, ) as bool); - @override bool canGoForward(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -263,7 +249,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: false, ) as bool); - @override void goBack(int? identifier) => super.noSuchMethod( Invocation.method( @@ -272,7 +257,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void goForward(int? identifier) => super.noSuchMethod( Invocation.method( @@ -281,7 +265,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void reload(int? identifier) => super.noSuchMethod( Invocation.method( @@ -290,13 +273,11 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getTitle(int? identifier) => (super.noSuchMethod(Invocation.method( #getTitle, [identifier], )) as String?); - @override void setAllowsBackForwardNavigationGestures( int? identifier, @@ -312,7 +293,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setCustomUserAgent( int? identifier, @@ -328,7 +308,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override _i4.Future evaluateJavaScript( int? identifier, @@ -344,7 +323,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: _i4.Future.value(), ) as _i4.Future); - @override void setInspectable( int? identifier, @@ -360,7 +338,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getCustomUserAgent(int? identifier) => (super.noSuchMethod(Invocation.method( @@ -393,7 +370,6 @@ class MockTestUIScrollViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override List getContentOffset(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -402,7 +378,6 @@ class MockTestUIScrollViewHostApi extends _i1.Mock ), returnValue: [], ) as List); - @override void scrollBy( int? identifier, @@ -420,7 +395,6 @@ class MockTestUIScrollViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setContentOffset( int? identifier, @@ -438,7 +412,6 @@ class MockTestUIScrollViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setDelegate( int? identifier, @@ -498,7 +471,6 @@ class MockTestUIViewHostApi extends _i1.Mock implements _i2.TestUIViewHostApi { ), returnValueForMissingStub: null, ); - @override void setOpaque( int? identifier, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart index e0eeb941cce9..d838bf996146 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart @@ -598,6 +598,34 @@ void main() { expect(policyData.value, WKNavigationActionPolicyEnum.cancel); }); + test('decidePolicyForNavigationResponse', () async { + WebKitFlutterApis.instance = WebKitFlutterApis( + instanceManager: instanceManager, + ); + + navigationDelegate = WKNavigationDelegate( + instanceManager: instanceManager, + decidePolicyForNavigationResponse: ( + WKWebView webView, + WKNavigationResponse navigationAction, + ) async { + return WKNavigationResponsePolicy.cancel; + }, + ); + + final WKNavigationResponsePolicyEnum policy = await WebKitFlutterApis + .instance.navigationDelegate + .decidePolicyForNavigationResponse( + instanceManager.getIdentifier(navigationDelegate)!, + instanceManager.getIdentifier(webView)!, + WKNavigationResponseData( + response: NSHttpUrlResponseData(statusCode: 401), + forMainFrame: true), + ); + + expect(policy, WKNavigationResponsePolicyEnum.cancel); + }); + test('didFailNavigation', () async { final Completer> argsCompleter = Completer>(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart index d9a0ed01d50d..99aedf1659f7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart @@ -47,7 +47,6 @@ class MockTestWKHttpCookieStoreHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override _i3.Future setCookie( int? identifier, @@ -109,7 +108,6 @@ class MockTestWKPreferencesHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setJavaScriptEnabled( int? identifier, @@ -189,7 +187,6 @@ class MockTestWKUserContentControllerHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addScriptMessageHandler( int? identifier, @@ -207,7 +204,6 @@ class MockTestWKUserContentControllerHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removeScriptMessageHandler( int? identifier, @@ -223,7 +219,6 @@ class MockTestWKUserContentControllerHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removeAllScriptMessageHandlers(int? identifier) => super.noSuchMethod( Invocation.method( @@ -232,7 +227,6 @@ class MockTestWKUserContentControllerHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addUserScript( int? identifier, @@ -248,7 +242,6 @@ class MockTestWKUserContentControllerHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removeAllUserScripts(int? identifier) => super.noSuchMethod( Invocation.method( @@ -276,7 +269,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void createFromWebView( int? identifier, @@ -292,7 +284,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setAllowsInlineMediaPlayback( int? identifier, @@ -308,7 +299,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setLimitsNavigationsToAppBoundDomains( int? identifier, @@ -324,7 +314,6 @@ class MockTestWKWebViewConfigurationHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setMediaTypesRequiringUserActionForPlayback( int? identifier, @@ -366,7 +355,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setUIDelegate( int? identifier, @@ -382,7 +370,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setNavigationDelegate( int? identifier, @@ -398,13 +385,11 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getUrl(int? identifier) => (super.noSuchMethod(Invocation.method( #getUrl, [identifier], )) as String?); - @override double getEstimatedProgress(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -413,7 +398,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: 0.0, ) as double); - @override void loadRequest( int? identifier, @@ -429,7 +413,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadHtmlString( int? identifier, @@ -447,7 +430,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadFileUrl( int? identifier, @@ -465,7 +447,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void loadFlutterAsset( int? identifier, @@ -481,7 +462,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override bool canGoBack(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -490,7 +470,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: false, ) as bool); - @override bool canGoForward(int? identifier) => (super.noSuchMethod( Invocation.method( @@ -499,7 +478,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: false, ) as bool); - @override void goBack(int? identifier) => super.noSuchMethod( Invocation.method( @@ -508,7 +486,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void goForward(int? identifier) => super.noSuchMethod( Invocation.method( @@ -517,7 +494,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void reload(int? identifier) => super.noSuchMethod( Invocation.method( @@ -526,13 +502,11 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getTitle(int? identifier) => (super.noSuchMethod(Invocation.method( #getTitle, [identifier], )) as String?); - @override void setAllowsBackForwardNavigationGestures( int? identifier, @@ -548,7 +522,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void setCustomUserAgent( int? identifier, @@ -564,7 +537,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override _i3.Future evaluateJavaScript( int? identifier, @@ -580,7 +552,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValue: _i3.Future.value(), ) as _i3.Future); - @override void setInspectable( int? identifier, @@ -596,7 +567,6 @@ class MockTestWKWebViewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override String? getCustomUserAgent(int? identifier) => (super.noSuchMethod(Invocation.method( @@ -629,7 +599,6 @@ class MockTestWKWebsiteDataStoreHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void createDefaultDataStore(int? identifier) => super.noSuchMethod( Invocation.method( @@ -638,7 +607,6 @@ class MockTestWKWebsiteDataStoreHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - @override _i3.Future removeDataOfTypes( int? identifier, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart index 171e4056feed..da8675886f7e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart @@ -71,6 +71,60 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); + test('setOnHttpError from decidePolicyForNavigationResponse', () { + final WebKitNavigationDelegate webKitDelegate = WebKitNavigationDelegate( + const WebKitNavigationDelegateCreationParams( + webKitProxy: WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, + createUIDelegate: CapturingUIDelegate.new, + ), + ), + ); + + late final HttpResponseError callbackError; + void onHttpError(HttpResponseError error) { + callbackError = error; + } + + webKitDelegate.setOnHttpError(onHttpError); + + CapturingNavigationDelegate + .lastCreatedDelegate.decidePolicyForNavigationResponse!( + WKWebView.detached(), + const WKNavigationResponse( + response: NSHttpUrlResponse(statusCode: 401), forMainFrame: true), + ); + + expect(callbackError.response?.statusCode, 401); + }); + + test('setOnHttpError is not called for error codes < 400', () { + final WebKitNavigationDelegate webKitDelegate = WebKitNavigationDelegate( + const WebKitNavigationDelegateCreationParams( + webKitProxy: WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, + createUIDelegate: CapturingUIDelegate.new, + ), + ), + ); + + HttpResponseError? callbackError; + void onHttpError(HttpResponseError error) { + callbackError = error; + } + + webKitDelegate.setOnHttpError(onHttpError); + + CapturingNavigationDelegate + .lastCreatedDelegate.decidePolicyForNavigationResponse!( + WKWebView.detached(), + const WKNavigationResponse( + response: NSHttpUrlResponse(statusCode: 399), forMainFrame: true), + ); + + expect(callbackError, isNull); + }); + test('onWebResourceError from didFailNavigation', () { final WebKitNavigationDelegate webKitDelegate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( @@ -263,6 +317,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate { CapturingNavigationDelegate({ super.didFinishNavigation, super.didStartProvisionalNavigation, + super.decidePolicyForNavigationResponse, super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart index a23eef8d8fbd..a28eca241ada 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart @@ -1538,6 +1538,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate { super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, + super.decidePolicyForNavigationResponse, super.webViewWebContentProcessDidTerminate, super.didReceiveAuthenticationChallenge, }) : super.detached() { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart index cc7b6932f66f..932c567fb1e5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart @@ -159,7 +159,6 @@ class MockNSUrl extends _i1.Mock implements _i2.NSUrl { ), returnValue: _i6.Future.value(), ) as _i6.Future); - @override _i2.NSObject copy() => (super.noSuchMethod( Invocation.method( @@ -174,7 +173,6 @@ class MockNSUrl extends _i1.Mock implements _i2.NSUrl { ), ), ) as _i2.NSObject); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -193,7 +191,6 @@ class MockNSUrl extends _i1.Mock implements _i2.NSUrl { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -233,7 +230,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { ), )), ) as _i6.Future<_i3.Point>); - @override _i6.Future scrollBy(_i3.Point? offset) => (super.noSuchMethod( Invocation.method( @@ -243,7 +239,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setContentOffset(_i3.Point? offset) => (super.noSuchMethod( @@ -254,7 +249,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setDelegate(_i4.UIScrollViewDelegate? delegate) => (super.noSuchMethod( @@ -265,7 +259,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i4.UIScrollView copy() => (super.noSuchMethod( Invocation.method( @@ -280,7 +273,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { ), ), ) as _i4.UIScrollView); - @override _i6.Future setBackgroundColor(_i7.Color? color) => (super.noSuchMethod( Invocation.method( @@ -290,7 +282,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( @@ -300,7 +291,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -319,7 +309,6 @@ class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -360,7 +349,6 @@ class MockUIScrollViewDelegate extends _i1.Mock ), ), ) as _i4.UIScrollViewDelegate); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -379,7 +367,6 @@ class MockUIScrollViewDelegate extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -414,7 +401,6 @@ class MockWKPreferences extends _i1.Mock implements _i5.WKPreferences { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i5.WKPreferences copy() => (super.noSuchMethod( Invocation.method( @@ -429,7 +415,6 @@ class MockWKPreferences extends _i1.Mock implements _i5.WKPreferences { ), ), ) as _i5.WKPreferences); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -448,7 +433,6 @@ class MockWKPreferences extends _i1.Mock implements _i5.WKPreferences { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -491,7 +475,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeScriptMessageHandler(String? name) => (super.noSuchMethod( @@ -502,7 +485,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( Invocation.method( @@ -512,7 +494,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future addUserScript(_i5.WKUserScript? userScript) => (super.noSuchMethod( @@ -523,7 +504,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeAllUserScripts() => (super.noSuchMethod( Invocation.method( @@ -533,7 +513,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i5.WKUserContentController copy() => (super.noSuchMethod( Invocation.method( @@ -548,7 +527,6 @@ class MockWKUserContentController extends _i1.Mock ), ), ) as _i5.WKUserContentController); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -567,7 +545,6 @@ class MockWKUserContentController extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -602,7 +579,6 @@ class MockWKWebsiteDataStore extends _i1.Mock Invocation.getter(#httpCookieStore), ), ) as _i5.WKHttpCookieStore); - @override _i6.Future removeDataOfTypes( Set<_i5.WKWebsiteDataType>? dataTypes, @@ -618,7 +594,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), returnValue: _i6.Future.value(false), ) as _i6.Future); - @override _i5.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( @@ -633,7 +608,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), ), ) as _i5.WKWebsiteDataStore); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -652,7 +626,6 @@ class MockWKWebsiteDataStore extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -686,7 +659,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { Invocation.getter(#configuration), ), ) as _i5.WKWebViewConfiguration); - @override _i4.UIScrollView get scrollView => (super.noSuchMethod( Invocation.getter(#scrollView), @@ -695,7 +667,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { Invocation.getter(#scrollView), ), ) as _i4.UIScrollView); - @override _i6.Future setUIDelegate(_i5.WKUIDelegate? delegate) => (super.noSuchMethod( @@ -706,7 +677,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setNavigationDelegate(_i5.WKNavigationDelegate? delegate) => (super.noSuchMethod( @@ -717,7 +687,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future getUrl() => (super.noSuchMethod( Invocation.method( @@ -726,7 +695,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future getEstimatedProgress() => (super.noSuchMethod( Invocation.method( @@ -735,7 +703,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(0.0), ) as _i6.Future); - @override _i6.Future loadRequest(_i2.NSUrlRequest? request) => (super.noSuchMethod( @@ -746,7 +713,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future loadHtmlString( String? string, { @@ -761,7 +727,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future loadFileUrl( String? url, { @@ -776,7 +741,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( @@ -786,7 +750,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future canGoBack() => (super.noSuchMethod( Invocation.method( @@ -795,7 +758,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(false), ) as _i6.Future); - @override _i6.Future canGoForward() => (super.noSuchMethod( Invocation.method( @@ -804,7 +766,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(false), ) as _i6.Future); - @override _i6.Future goBack() => (super.noSuchMethod( Invocation.method( @@ -814,7 +775,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future goForward() => (super.noSuchMethod( Invocation.method( @@ -824,7 +784,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future reload() => (super.noSuchMethod( Invocation.method( @@ -834,7 +793,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future getTitle() => (super.noSuchMethod( Invocation.method( @@ -843,7 +801,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( @@ -854,7 +811,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( @@ -864,7 +820,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future evaluateJavaScript(String? javaScriptString) => (super.noSuchMethod( @@ -874,7 +829,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setInspectable(bool? inspectable) => (super.noSuchMethod( Invocation.method( @@ -884,7 +838,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future getCustomUserAgent() => (super.noSuchMethod( Invocation.method( @@ -893,7 +846,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), returnValue: _i6.Future.value(), ) as _i6.Future); - @override _i5.WKWebView copy() => (super.noSuchMethod( Invocation.method( @@ -908,7 +860,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { ), ), ) as _i5.WKWebView); - @override _i6.Future setBackgroundColor(_i7.Color? color) => (super.noSuchMethod( Invocation.method( @@ -918,7 +869,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( @@ -928,7 +878,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -947,7 +896,6 @@ class MockWKWebView extends _i1.Mock implements _i5.WKWebView { returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -982,7 +930,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#userContentController), ), ) as _i5.WKUserContentController); - @override _i5.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), @@ -991,7 +938,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#preferences), ), ) as _i5.WKPreferences); - @override _i5.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), @@ -1000,7 +946,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#websiteDataStore), ), ) as _i5.WKWebsiteDataStore); - @override _i6.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( @@ -1011,7 +956,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setLimitsNavigationsToAppBoundDomains(bool? limit) => (super.noSuchMethod( @@ -1022,7 +966,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future setMediaTypesRequiringUserActionForPlayback( Set<_i5.WKAudiovisualMediaType>? types) => @@ -1034,7 +977,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i5.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( @@ -1049,7 +991,6 @@ class MockWKWebViewConfiguration extends _i1.Mock ), ), ) as _i5.WKWebViewConfiguration); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -1068,7 +1009,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { @@ -1109,7 +1049,6 @@ class MockWKScriptMessageHandler extends _i1.Mock _i5.WKUserContentController, _i5.WKScriptMessage, )); - @override _i5.WKScriptMessageHandler copy() => (super.noSuchMethod( Invocation.method( @@ -1124,7 +1063,6 @@ class MockWKScriptMessageHandler extends _i1.Mock ), ), ) as _i5.WKScriptMessageHandler); - @override _i6.Future addObserver( _i2.NSObject? observer, { @@ -1143,7 +1081,6 @@ class MockWKScriptMessageHandler extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); - @override _i6.Future removeObserver( _i2.NSObject? observer, { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart index bda15f7cec78..d9d21dab8db5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart @@ -63,7 +63,6 @@ class MockWKWebsiteDataStore extends _i1.Mock Invocation.getter(#httpCookieStore), ), ) as _i2.WKHttpCookieStore); - @override _i3.Future removeDataOfTypes( Set<_i2.WKWebsiteDataType>? dataTypes, @@ -79,7 +78,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), returnValue: _i3.Future.value(false), ) as _i3.Future); - @override _i2.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( @@ -94,7 +92,6 @@ class MockWKWebsiteDataStore extends _i1.Mock ), ), ) as _i2.WKWebsiteDataStore); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -113,7 +110,6 @@ class MockWKWebsiteDataStore extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { @@ -148,7 +144,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i2.WKHttpCookieStore copy() => (super.noSuchMethod( Invocation.method( @@ -163,7 +158,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { ), ), ) as _i2.WKHttpCookieStore); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -182,7 +176,6 @@ class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart index 2aff8e964b60..8c511d9f8025 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart @@ -99,7 +99,6 @@ class MockWKUIDelegate extends _i1.Mock implements _i2.WKUIDelegate { ), ), ) as _i2.WKUIDelegate); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -118,7 +117,6 @@ class MockWKUIDelegate extends _i1.Mock implements _i2.WKUIDelegate { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { @@ -153,7 +151,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#userContentController), ), ) as _i2.WKUserContentController); - @override _i2.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), @@ -162,7 +159,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#preferences), ), ) as _i2.WKPreferences); - @override _i2.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), @@ -171,7 +167,6 @@ class MockWKWebViewConfiguration extends _i1.Mock Invocation.getter(#websiteDataStore), ), ) as _i2.WKWebsiteDataStore); - @override _i3.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( @@ -182,7 +177,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future setLimitsNavigationsToAppBoundDomains(bool? limit) => (super.noSuchMethod( @@ -193,7 +187,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future setMediaTypesRequiringUserActionForPlayback( Set<_i2.WKAudiovisualMediaType>? types) => @@ -205,7 +198,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i2.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( @@ -220,7 +212,6 @@ class MockWKWebViewConfiguration extends _i1.Mock ), ), ) as _i2.WKWebViewConfiguration); - @override _i3.Future addObserver( _i4.NSObject? observer, { @@ -239,7 +230,6 @@ class MockWKWebViewConfiguration extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future removeObserver( _i4.NSObject? observer, { diff --git a/script/configs/allowed_unpinned_deps.yaml b/script/configs/allowed_unpinned_deps.yaml index 7387401c5313..84ff94a1101b 100644 --- a/script/configs/allowed_unpinned_deps.yaml +++ b/script/configs/allowed_unpinned_deps.yaml @@ -6,10 +6,6 @@ ## Explicit allowances -# Temporary during transition to local_auth_darwin. Can be removed once -# the default endorsement changes. -- local_auth_ios - # Owned by individual Flutter Team members. # Ideally we would not do this, since there's no clear plan for what # would happen if the individuals left the Flutter Team, and the @@ -29,9 +25,11 @@ - build_config - build_runner - build_test +- code_builder - collection - convert - crypto +- dart_style - fake_async - ffi - gcloud diff --git a/script/configs/exclude_integration_web.yaml b/script/configs/exclude_integration_web.yaml index 6c0fc4efcb7a..59b7d8fae145 100644 --- a/script/configs/exclude_integration_web.yaml +++ b/script/configs/exclude_integration_web.yaml @@ -1,2 +1,4 @@ # Currently missing: https://github.com/flutter/flutter/issues/82211 - file_selector +# Waiting on https://github.com/flutter/flutter/issues/145149 +- google_maps_flutter diff --git a/script/tool/lib/src/common/package_command.dart b/script/tool/lib/src/common/package_command.dart index f51c011ec036..94a808afc17b 100644 --- a/script/tool/lib/src/common/package_command.dart +++ b/script/tool/lib/src/common/package_command.dart @@ -50,7 +50,11 @@ abstract class PackageCommand extends Command { argParser.addMultiOption( _packagesArg, help: - 'Specifies which packages the command should run on (before sharding).\n', + 'Specifies which packages the command should run on (before sharding).\n' + 'If a package name is the name of a plugin group, it will include ' + 'the entire group; to avoid this, use group/package as the name ' + '(e.g., shared_preferences/shared_preferences), or pass ' + '--$_exactMatchOnlyArg', valueHelp: 'package1,package2,...', aliases: [_pluginsLegacyAliasArg], ); @@ -67,6 +71,9 @@ abstract class PackageCommand extends Command { valueHelp: 'n', defaultsTo: '1', ); + argParser.addFlag(_exactMatchOnlyArg, + help: 'Disables package group matching in package selection.', + negatable: false); argParser.addMultiOption( _excludeArg, abbr: 'e', @@ -136,6 +143,7 @@ abstract class PackageCommand extends Command { static const String _pluginsLegacyAliasArg = 'plugins'; static const String _runOnChangedPackagesArg = 'run-on-changed-packages'; static const String _runOnDirtyPackagesArg = 'run-on-dirty-packages'; + static const String _exactMatchOnlyArg = 'exact-match-only'; static const String _excludeArg = 'exclude'; static const String _filterPackagesArg = 'filter-packages-to'; // Diff base selection. @@ -361,6 +369,15 @@ abstract class PackageCommand extends Command { throw ToolExit(exitInvalidArguments); } + // Whether to require that a package name exactly match to be included, + // rather than allowing package groups for federated plugins. Any cases + // where the set of packages is determined programatically based on repo + // state should use exact matching. + final bool allowGroupMatching = !(getBoolArg(_exactMatchOnlyArg) || + argResults!.wasParsed(_runOnChangedPackagesArg) || + argResults!.wasParsed(_runOnDirtyPackagesArg) || + argResults!.wasParsed(_packagesForBranchArg)); + Set packages = Set.from(getStringListArg(_packagesArg)); final GitVersionFinder? changedFileFinder; @@ -458,6 +475,30 @@ abstract class PackageCommand extends Command { excludeAllButPackageNames.intersection(possibleNames).isEmpty; } + await for (final RepositoryPackage package in _everyTopLevelPackage()) { + if (packages.isEmpty || + packages + .intersection(_possiblePackageIdentifiers(package, + allowGroup: allowGroupMatching)) + .isNotEmpty) { + // Exclusion is always human input, so groups should always be allowed + // unless they have been specifically forbidden. + final bool excluded = isExcluded(_possiblePackageIdentifiers(package, + allowGroup: !getBoolArg(_exactMatchOnlyArg))); + yield PackageEnumerationEntry(package, excluded: excluded); + } + } + } + + /// Returns every top-level package in the repository, according to repository + /// conventions. + /// + /// In particular, it returns: + /// - Every package that is a direct child of one of the know "packages" + /// directories. + /// - Every package that is a direct child of a non-package subdirectory of + /// one of those directories (to cover federated plugin groups). + Stream _everyTopLevelPackage() async* { for (final Directory dir in [ packagesDir, if (thirdPartyPackagesDir.existsSync()) thirdPartyPackagesDir, @@ -466,33 +507,14 @@ abstract class PackageCommand extends Command { in dir.list(followLinks: false)) { // A top-level Dart package is a standard package. if (isPackage(entity)) { - if (packages.isEmpty || packages.contains(p.basename(entity.path))) { - yield PackageEnumerationEntry( - RepositoryPackage(entity as Directory), - excluded: isExcluded({entity.basename})); - } + yield RepositoryPackage(entity as Directory); } else if (entity is Directory) { // Look for Dart packages under this top-level directory; this is the // standard structure for federated plugins. await for (final FileSystemEntity subdir in entity.list(followLinks: false)) { if (isPackage(subdir)) { - // There are three ways for a federated plugin to match: - // - package name (path_provider_android) - // - fully specified name (path_provider/path_provider_android) - // - group name (path_provider), which matches all packages in - // the group - final Set possibleMatches = { - path.basename(subdir.path), // package name - path.basename(entity.path), // group name - path.relative(subdir.path, from: dir.path), // fully specified - }; - if (packages.isEmpty || - packages.intersection(possibleMatches).isNotEmpty) { - yield PackageEnumerationEntry( - RepositoryPackage(subdir as Directory), - excluded: isExcluded(possibleMatches)); - } + yield RepositoryPackage(subdir as Directory); } } } @@ -500,6 +522,29 @@ abstract class PackageCommand extends Command { } } + Set _possiblePackageIdentifiers( + RepositoryPackage package, { + required bool allowGroup, + }) { + final String packageName = path.basename(package.path); + if (package.isFederated) { + // There are three ways for a federated plugin to be identified: + // - package name (path_provider_android). + // - fully specified name (path_provider/path_provider_android). + // - group name (path_provider), which includes all packages in + // the group. + final io.Directory parentDir = package.directory.parent; + return { + packageName, + path.relative(package.path, + from: parentDir.parent.path), // fully specified + if (allowGroup) path.basename(parentDir.path), // group name + }; + } else { + return {packageName}; + } + } + /// Returns all Dart package folders (typically, base package + example) of /// the packages involved in this command execution. /// diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart index f2713666435b..3091cf2e11f3 100644 --- a/script/tool/lib/src/format_command.dart +++ b/script/tool/lib/src/format_command.dart @@ -346,6 +346,8 @@ class FormatCommand extends PackageCommand { pathFragmentForDirectories(['example', 'build'])) && // Ignore files in Pods, which are not part of the repository. !path.contains(pathFragmentForDirectories(['Pods'])) && + // See https://github.com/flutter/flutter/issues/144039 + !path.endsWith('GeneratedPluginRegistrant.swift') && // Ignore .dart_tool/, which can have various intermediate files. !path.contains(pathFragmentForDirectories(['.dart_tool']))) .toList(); diff --git a/script/tool/lib/src/publish_command.dart b/script/tool/lib/src/publish_command.dart index eedea90e737c..b678e36b7aba 100644 --- a/script/tool/lib/src/publish_command.dart +++ b/script/tool/lib/src/publish_command.dart @@ -58,6 +58,11 @@ class PublishCommand extends PackageLoopingCommand { }) : _pubVersionFinder = PubVersionFinder(httpClient: httpClient ?? http.Client()), _stdin = stdinput ?? io.stdin { + argParser.addFlag(_alreadyTaggedFlag, + help: + 'Instead of tagging, validates that the current checkout is already tagged with the expected version.\n' + 'This is primarily intended for use in CI publish steps triggered by tagging.', + negatable: false); argParser.addMultiOption(_pubFlagsOption, help: 'A list of options that will be forwarded on to pub. Separate multiple flags with commas.'); @@ -83,13 +88,20 @@ class PublishCommand extends PackageLoopingCommand { argParser.addFlag(_skipConfirmationFlag, help: 'Run the command without asking for Y/N inputs.\n' 'This command will add a `--force` flag to the `pub publish` command if it is not added with $_pubFlagsOption\n'); + argParser.addFlag(_tagForAutoPublishFlag, + help: + 'Runs the dry-run publish, and tags if it succeeds, but does not actually publish.\n' + 'This is intended for use with a separate publish step that is based on tag push events.', + negatable: false); } + static const String _alreadyTaggedFlag = 'already-tagged'; static const String _pubFlagsOption = 'pub-publish-flags'; static const String _remoteOption = 'remote'; static const String _allChangedFlag = 'all-changed'; static const String _dryRunFlag = 'dry-run'; static const String _skipConfirmationFlag = 'skip-confirmation'; + static const String _tagForAutoPublishFlag = 'tag-for-auto-publish'; static const String _pubCredentialName = 'PUB_CREDENTIALS'; @@ -193,15 +205,27 @@ class PublishCommand extends PackageLoopingCommand { return PackageResult.fail(['uncommitted changes']); } - if (!await _publish(package)) { - return PackageResult.fail(['publish failed']); + final bool tagOnly = getBoolArg(_tagForAutoPublishFlag); + if (!tagOnly) { + if (!await _publish(package)) { + return PackageResult.fail(['publish failed']); + } } - if (!await _tagRelease(package)) { - return PackageResult.fail(['tagging failed']); + final String tag = _getTag(package); + if (getBoolArg(_alreadyTaggedFlag)) { + if (!(await _getCurrentTags()).contains(tag)) { + printError('The current checkout is not already tagged "$tag"'); + return PackageResult.fail(['missing tag']); + } + } else { + if (!await _tagRelease(package, tag)) { + return PackageResult.fail(['tagging failed']); + } } - print('\nPublished ${package.directory.basename} successfully!'); + final String action = tagOnly ? 'Tagged' : 'Published'; + print('\n$action ${package.directory.basename} successfully!'); return PackageResult.success(); } @@ -277,8 +301,7 @@ Safe to ignore if the package is deleted in this commit. // Tag the release with -v, and push it to the remote. // // Return `true` if successful, `false` otherwise. - Future _tagRelease(RepositoryPackage package) async { - final String tag = _getTag(package); + Future _tagRelease(RepositoryPackage package, String tag) async { print('Tagging release $tag...'); if (!getBoolArg(_dryRunFlag)) { final io.ProcessResult result = await (await gitDir).runCommand( @@ -301,6 +324,22 @@ Safe to ignore if the package is deleted in this commit. return success; } + Future> _getCurrentTags() async { + // git tag --points-at HEAD + final io.ProcessResult tagsResult = await (await gitDir).runCommand( + ['tag', '--points-at', 'HEAD'], + throwOnError: false, + ); + if (tagsResult.exitCode != 0) { + return []; + } + + return (tagsResult.stdout as String) + .split('\n') + .map((String line) => line.trim()) + .where((String line) => line.isNotEmpty); + } + Future _checkGitStatus(RepositoryPackage package) async { final io.ProcessResult statusResult = await (await gitDir).runCommand( [ diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart index a71fc2a8eace..f80db4181d34 100644 --- a/script/tool/test/common/package_command_test.dart +++ b/script/tool/test/common/package_command_test.dart @@ -222,11 +222,6 @@ void main() { test( 'explicitly specifying the plugin (group) name of a federated plugin ' 'should include all plugins in the group', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - FakeProcessInfo(MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -''')), - ]; final Directory pluginGroup = packagesDir.childDirectory('plugin1'); final RepositoryPackage appFacingPackage = createFakePlugin('plugin1', pluginGroup); @@ -235,8 +230,7 @@ packages/plugin1/plugin1/plugin1.dart final RepositoryPackage implementationPackage = createFakePlugin('plugin1_web', pluginGroup); - await runCapturingPrint( - runner, ['sample', '--base-sha=main', '--packages=plugin1']); + await runCapturingPrint(runner, ['sample', '--packages=plugin1']); expect( command.plugins, @@ -247,6 +241,21 @@ packages/plugin1/plugin1/plugin1.dart ])); }); + test( + 'specifying the app-facing package of a federated plugin with ' + '--exact-match-only should only include only that package', () async { + final Directory pluginGroup = packagesDir.childDirectory('plugin1'); + final RepositoryPackage appFacingPackage = + createFakePlugin('plugin1', pluginGroup); + createFakePlugin('plugin1_platform_interface', pluginGroup); + createFakePlugin('plugin1_web', pluginGroup); + + await runCapturingPrint(runner, + ['sample', '--packages=plugin1', '--exact-match-only']); + + expect(command.plugins, unorderedEquals([appFacingPackage.path])); + }); + test( 'specifying the app-facing package of a federated plugin using its ' 'fully qualified name should include only that package', () async { diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index cc6019862bae..d05aa14c2de8 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -737,6 +737,52 @@ void main() { ])); }); + test('skips GeneratedPluginRegistrant.swift', () async { + const String sourceFile = 'macos/Classes/Foo.swift'; + final RepositoryPackage plugin = createFakePlugin( + 'a_plugin', + packagesDir, + extraFiles: [ + sourceFile, + 'example/macos/Flutter/GeneratedPluginRegistrant.swift', + ], + ); + + await runCapturingPrint(runner, [ + 'format', + '--swift', + '--swift-format-path=/path/to/swift-format' + ]); + + expect( + processRunner.recordedCalls, + orderedEquals([ + const ProcessCall( + '/path/to/swift-format', + ['--version'], + null, + ), + ProcessCall( + '/path/to/swift-format', + [ + '-i', + ...getPackagesDirRelativePaths(plugin, [sourceFile]) + ], + packagesDir.path, + ), + ProcessCall( + '/path/to/swift-format', + [ + 'lint', + '--parallel', + '--strict', + ...getPackagesDirRelativePaths(plugin, [sourceFile]), + ], + packagesDir.path, + ), + ])); + }); + test('fails if files are changed with --fail-on-change', () async { const List files = [ 'linux/foo_plugin.cc', diff --git a/script/tool/test/publish_command_test.dart b/script/tool/test/publish_command_test.dart index 3af74a1607bc..1f3c0958b92d 100644 --- a/script/tool/test/publish_command_test.dart +++ b/script/tool/test/publish_command_test.dart @@ -349,6 +349,33 @@ void main() { ), ); }); + + test('skips publish with --tag-for-auto-publish', () async { + const String packageName = 'a_package'; + createFakePackage(packageName, packagesDir); + + final List output = + await runCapturingPrint(commandRunner, [ + 'publish', + '--packages=$packageName', + '--tag-for-auto-publish', + ]); + + // There should be no variant of any command containing "publish". + expect( + processRunner.recordedCalls + .map((ProcessCall call) => call.toString()), + isNot(contains(contains('publish')))); + // The output should indicate that it was tagged, not published. + expect( + output, + containsAllInOrder( + [ + contains('Tagged a_package successfully!'), + ], + ), + ); + }); }); group('Tags release', () { @@ -390,6 +417,18 @@ void main() { isNot(contains( const ProcessCall('git-tag', ['foo-v0.0.1'], null)))); }); + + test('when passed --tag-for-auto-publish', () async { + createFakePlugin('foo', packagesDir, examples: []); + await runCapturingPrint(commandRunner, [ + 'publish', + '--packages=foo', + '--tag-for-auto-publish', + ]); + + expect(processRunner.recordedCalls, + contains(const ProcessCall('git-tag', ['foo-v0.0.1'], null))); + }); }); group('Pushes tags', () { @@ -439,6 +478,21 @@ void main() { ])); }); + test('when passed --tag-for-auto-publish', () async { + createFakePlugin('foo', packagesDir, examples: []); + await runCapturingPrint(commandRunner, [ + 'publish', + '--packages=foo', + '--skip-confirmation', + '--tag-for-auto-publish', + ]); + + expect( + processRunner.recordedCalls, + contains(const ProcessCall( + 'git-push', ['upstream', 'foo-v0.0.1'], null))); + }); + test('to upstream by default, dry run', () async { final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, examples: []); @@ -488,6 +542,62 @@ void main() { }); }); + group('--already-tagged', () { + test('passes when HEAD has the expected tag', () async { + createFakePlugin('foo', packagesDir, examples: []); + + processRunner.mockProcessesForExecutable['git-tag'] = [ + FakeProcessInfo(MockProcess()), // Skip the initializeRun call. + FakeProcessInfo(MockProcess(stdout: 'foo-v0.0.1\n'), + ['--points-at', 'HEAD']) + ]; + + await runCapturingPrint(commandRunner, + ['publish', '--packages=foo', '--already-tagged']); + }); + + test('fails if HEAD does not have the expected tag', () async { + createFakePlugin('foo', packagesDir, examples: []); + + Error? commandError; + final List output = await runCapturingPrint(commandRunner, + ['publish', '--packages=foo', '--already-tagged'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('The current checkout is not already tagged "foo-v0.0.1"'), + contains('missing tag'), + ])); + }); + + test('does not create or push tags', () async { + createFakePlugin('foo', packagesDir, examples: []); + + processRunner.mockProcessesForExecutable['git-tag'] = [ + FakeProcessInfo(MockProcess()), // Skip the initializeRun call. + FakeProcessInfo(MockProcess(stdout: 'foo-v0.0.1\n'), + ['--points-at', 'HEAD']) + ]; + + await runCapturingPrint(commandRunner, + ['publish', '--packages=foo', '--already-tagged']); + + expect( + processRunner.recordedCalls, + isNot(contains( + const ProcessCall('git-tag', ['foo-v0.0.1'], null)))); + expect( + processRunner.recordedCalls + .map((ProcessCall call) => call.executable), + isNot(contains('git-push'))); + }); + }); + group('Auto release (all-changed flag)', () { test('can release newly created plugins', () async { mockHttpResponses['plugin1'] = {