diff --git a/.ci.yaml b/.ci.yaml index a65b1785fdd1..e020151499b9 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -83,7 +83,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "gems", "version": "v3.3.14"} + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] os: "Mac-12|Mac-13" device_type: none @@ -96,7 +96,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "gems", "version": "v3.3.14"} + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] os: "Mac-12|Mac-13" device_type: none @@ -271,7 +271,7 @@ targets: # web_benchmarks needs Chrome. dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"}, + {"dependency": "android_virtual_device", "version": "34"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "chrome_and_driver", "version": "version:114.0"} ] @@ -288,7 +288,7 @@ targets: # See comments on 'master' version above. dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"}, + {"dependency": "android_virtual_device", "version": "34"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "chrome_and_driver", "version": "version:114.0"} ] @@ -323,6 +323,9 @@ targets: {"dependency": "open_jdk", "version": "version:11"} ] + # All of the Linux_android android_platform_tests shards have the same + # dependency list, despite some running on Android 33 AVDs versus 34. + # See https://github.com/flutter/flutter/issues/137082 for context. - name: Linux_android android_platform_tests_shard_1 master recipe: packages/packages timeout: 60 @@ -330,10 +333,11 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 0 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_2 master @@ -343,10 +347,11 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 1 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_3 master @@ -356,10 +361,11 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 2 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_4 master @@ -369,10 +375,11 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 3 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_5 master @@ -382,10 +389,11 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 4 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_6 master @@ -395,20 +403,21 @@ targets: target_file: android_platform_tests.yaml channel: master version_file: flutter_master.version + # set up for 34 package_sharding: "--shardIndex 5 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_api_33_shard_1 master recipe: packages/packages timeout: 60 - bringup: true properties: - target_file: android_platform_tests_api_33.yaml + target_file: android_platform_tests_api_33_avd.yaml channel: master version_file: flutter_master.version + # set up for 33 package_sharding: "--shardIndex 0 --shardCount 2" dependencies: >- [ @@ -418,11 +427,11 @@ targets: - name: Linux_android android_platform_tests_api_33_shard_2 master recipe: packages/packages timeout: 60 - bringup: true properties: - target_file: android_platform_tests_api_33.yaml + target_file: android_platform_tests_api_33_avd.yaml channel: master version_file: flutter_master.version + # set up for 33 package_sharding: "--shardIndex 1 --shardCount 2" dependencies: >- [ @@ -440,7 +449,7 @@ targets: package_sharding: "--shardIndex 0 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_2 stable @@ -454,7 +463,7 @@ targets: package_sharding: "--shardIndex 1 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_3 stable @@ -468,7 +477,7 @@ targets: package_sharding: "--shardIndex 2 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_4 stable @@ -482,7 +491,7 @@ targets: package_sharding: "--shardIndex 3 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_5 stable @@ -496,7 +505,7 @@ targets: package_sharding: "--shardIndex 4 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_shard_6 stable @@ -510,15 +519,14 @@ targets: package_sharding: "--shardIndex 5 --shardCount 6" dependencies: >- [ - {"dependency": "android_virtual_device", "version": "33"} + {"dependency": "android_virtual_device", "version": "34"} ] - name: Linux_android android_platform_tests_api_33_shard_1 stable recipe: packages/packages timeout: 60 - bringup: true properties: - target_file: android_platform_tests_api_33.yaml + target_file: android_platform_tests_api_33_avd.yaml channel: master version_file: flutter_stable.version package_sharding: "--shardIndex 0 --shardCount 2" @@ -530,9 +538,8 @@ targets: - name: Linux_android android_platform_tests_api_33_shard_2 stable recipe: packages/packages timeout: 60 - bringup: true properties: - target_file: android_platform_tests_api_33.yaml + target_file: android_platform_tests_api_33_avd.yaml channel: master version_file: flutter_stable.version package_sharding: "--shardIndex 1 --shardCount 2" @@ -541,60 +548,54 @@ targets: {"dependency": "android_virtual_device", "version": "33"} ] - - name: linux_android_legacy android_platform_tests_legacy_api_shard_1 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_1 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master version_file: flutter_master.version package_sharding: "--shardIndex 0 --shardCount 6" - - name: linux_android_legacy android_platform_tests_legacy_api_shard_2 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_2 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master version_file: flutter_master.version package_sharding: "--shardIndex 1 --shardCount 6" - - name: linux_android_legacy android_platform_tests_legacy_api_shard_3 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_3 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master version_file: flutter_master.version package_sharding: "--shardIndex 2 --shardCount 6" - - name: linux_android_legacy android_platform_tests_legacy_api_shard_4 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_4 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master version_file: flutter_master.version package_sharding: "--shardIndex 3 --shardCount 6" - - name: linux_android_legacy android_platform_tests_legacy_api_shard_5 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_5 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master version_file: flutter_master.version package_sharding: "--shardIndex 4 --shardCount 6" - - name: linux_android_legacy android_platform_tests_legacy_api_shard_6 master + - name: Linux_android_legacy android_platform_tests_legacy_api_shard_6 master recipe: packages/packages timeout: 60 - bringup: true # New target properties: target_file: android_legacy_emulator_tests.yaml channel: master @@ -608,7 +609,7 @@ targets: - name: Linux_android android_device_tests_shard_1 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -617,7 +618,7 @@ targets: - name: Linux_android android_device_tests_shard_2 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -626,7 +627,7 @@ targets: - name: Linux_android android_device_tests_shard_3 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -635,7 +636,7 @@ targets: - name: Linux_android android_device_tests_shard_4 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -644,7 +645,7 @@ targets: - name: Linux_android android_device_tests_shard_5 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -653,7 +654,7 @@ targets: - name: Linux_android android_device_tests_shard_6 master recipe: packages/packages - timeout: 60 + timeout: 90 properties: target_file: android_device_tests.yaml channel: master @@ -1100,20 +1101,6 @@ targets: {"dependency": "vs_build", "version": "version:vs2019"} ] - - name: Windows_arm64 windows-build_all_packages stable - recipe: packages/packages - timeout: 30 - bringup: true # https://github.com/flutter/flutter/issues/134083 - properties: - add_recipes_cq: "true" - target_file: windows_build_all_packages.yaml - channel: stable - version_file: flutter_stable.version - dependencies: > - [ - {"dependency": "vs_build", "version": "version:vs2019"} - ] - - name: Windows_x64 repo_tools_tests recipe: packages/packages timeout: 30 diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index f008f1149667..7b74206a9cb6 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -5e8b5f4ea293b092e583c0fa9ad82fe189a72aa5 +5a6a3224fe1502dc2f1f05c13268f3e96bc89461 diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index 744ec1b1c8a7..d4ac38647b94 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e +d211f42860350d914a5ad8102f9ec32764dc6d06 diff --git a/.ci/targets/android_device_tests.yaml b/.ci/targets/android_device_tests.yaml index 6720a03d88a3..c626479504fd 100644 --- a/.ci/targets/android_device_tests.yaml +++ b/.ci/targets/android_device_tests.yaml @@ -11,7 +11,7 @@ tasks: args: - "firebase-test-lab" - "--device" - - "model=oriole,version=33" + - "model=panther,version=33" - "--exclude=script/configs/exclude_integration_android.yaml" - "--project=flutter-infra-staging" - "--results-bucket=flutter_firebase_testlab_staging" diff --git a/.ci/targets/android_platform_tests.yaml b/.ci/targets/android_platform_tests.yaml index eea5063cb713..7e27f17d5819 100644 --- a/.ci/targets/android_platform_tests.yaml +++ b/.ci/targets/android_platform_tests.yaml @@ -5,23 +5,23 @@ tasks: - name: download Dart and Android deps script: script/tool_runner.sh infra_step: true - args: ["fetch-deps", "--android", "--supporting-target-platforms-only"] + args: ["fetch-deps", "--android", "--supporting-target-platforms-only", "--exclude=script/configs/still_requires_api_33_avd.yaml"] - name: build examples script: script/tool_runner.sh - args: ["build-examples", "--apk"] + args: ["build-examples", "--apk", "--exclude=script/configs/still_requires_api_33_avd.yaml"] - name: lint script: script/tool_runner.sh - args: ["lint-android"] + args: ["lint-android", "--exclude=script/configs/still_requires_api_33_avd.yaml"] # Native unit and native integration are split into two steps to allow for # different exclusions. # TODO(stuartmorgan): Eliminate the native unit test exclusion, and combine # these steps. - name: native unit tests script: script/tool_runner.sh - args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml"] + args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml,script/configs/still_requires_api_33_avd.yaml"] - name: native integration tests script: script/tool_runner.sh - args: ["native-test", "--android", "--no-unit"] + args: ["native-test", "--android", "--no-unit", "--exclude=script/configs/still_requires_api_33_avd.yaml"] - name: drive examples script: script/tool_runner.sh - args: ["drive-examples", "--android", "--exclude=script/configs/exclude_integration_android.yaml,script/configs/exclude_integration_android_emulator.yaml"] + args: ["drive-examples", "--android", "--exclude=script/configs/exclude_integration_android.yaml,script/configs/exclude_integration_android_emulator.yaml,script/configs/still_requires_api_33_avd.yaml"] diff --git a/.ci/targets/android_platform_tests_api_33_avd.yaml b/.ci/targets/android_platform_tests_api_33_avd.yaml index cbec7c79127b..bebcb9a93b92 100644 --- a/.ci/targets/android_platform_tests_api_33_avd.yaml +++ b/.ci/targets/android_platform_tests_api_33_avd.yaml @@ -20,11 +20,11 @@ tasks: # these steps. - name: native unit tests script: script/tool_runner.sh - args: ["native-test", "--android", "--no-integration", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] + args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] - name: native integration tests script: script/tool_runner.sh args: ["native-test", "--android", "--no-unit", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] - name: drive examples script: script/tool_runner.sh - args: ["drive-examples", "--android", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] + args: ["drive-examples", "--android", "--exclude=script/configs/exclude_integration_android.yaml,script/configs/exclude_integration_android_emulator.yaml", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index e566748fc7f8..607b0c999cbb 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.0.3 + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.0.3 with: results_file: results.sarif results_format: sarif @@ -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@49abf0ba24d0b7953cb586944e918a0b92074c80 # v1.0.26 + uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v1.0.26 with: sarif_file: results.sarif diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index ef2cc5f49fa7..acaa35064435 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.8+13 + +* Updates annotations lib to 1.7.0. + ## 0.10.8+12 * Fixes handling of autofocus state when taking a picture. diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index faddec9d54f6..2c14eb03c984 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.5.0' + implementation 'androidx.annotation:annotation:1.7.0' 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 86f3665dc082..f920e2336fa2 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+12 +version: 0.10.8+13 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 995e91706883..fc9c6babd1a9 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.5.0+22 + +* Fixes `_getResolutionSelectorFromPreset` null pointer error. + +## 0.5.0+21 + +* Changes fallback resolution strategies for camera use cases to look for a higher resolution if neither the desired + resolution nor any lower resolutions are available. + +## 0.5.0+20 + +* Implements `setZoomLevel`. + ## 0.5.0+19 * Implements torch flash mode. diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index a9a3ddc305f2..073daa16d0eb 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -43,10 +43,6 @@ and thus, the plugin will fall back to 480p if configured with a `setFocusMode` & `setFocusPoint` are unimplemented. -### Zoom configuration \[[Issue #125371][125371]\] - -`setZoomLevel` is 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/CameraControlHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java index 8241fdaad9a3..e70714a8bffe 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 @@ -51,6 +51,29 @@ public void onFailure(Throwable t) { }, ContextCompat.getMainExecutor(context)); } + + /** Sets the zoom ratio of the specified {@link CameraControl} instance. */ + @NonNull + public void setZoomRatio( + @NonNull CameraControl cameraControl, + @NonNull Double ratio, + @NonNull GeneratedCameraXLibrary.Result result) { + float ratioAsFloat = ratio.floatValue(); + ListenableFuture setZoomRatioFuture = cameraControl.setZoomRatio(ratioAsFloat); + + Futures.addCallback( + setZoomRatioFuture, + new FutureCallback() { + public void onSuccess(Void voidResult) { + result.success(null); + } + + public void onFailure(Throwable t) { + result.error(t); + } + }, + ContextCompat.getMainExecutor(context)); + } } /** @@ -81,7 +104,8 @@ public CameraControlHostApiImpl( } /** - * Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode. + * Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode + * and set the zoom ratio. * *

If using the camera plugin in an add-to-app context, ensure that a new instance of the * {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes. @@ -98,4 +122,13 @@ public void enableTorch( proxy.enableTorch( Objects.requireNonNull(instanceManager.getInstance(identifier)), torch, result); } + + @Override + public void setZoomRatio( + @NonNull Long identifier, + @NonNull Double ratio, + @NonNull GeneratedCameraXLibrary.Result result) { + proxy.setZoomRatio( + Objects.requireNonNull(instanceManager.getInstance(identifier)), ratio, result); + } } 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 224ba11e0385..603af1f42a02 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 @@ -3237,6 +3237,9 @@ public interface CameraControlHostApi { void enableTorch( @NonNull Long identifier, @NonNull Boolean torch, @NonNull Result result); + void setZoomRatio( + @NonNull Long identifier, @NonNull Double ratio, @NonNull Result result); + /** The codec used by CameraControlHostApi. */ static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); @@ -3280,6 +3283,41 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.CameraControlHostApi.setZoomRatio", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + Double ratioArg = (Double) args.get(1); + Result resultCallback = + new Result() { + public void success(Void result) { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setZoomRatio( + (identifierArg == null) ? null : identifierArg.longValue(), + ratioArg, + resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ 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 5e17f6441367..a01e3db8238b 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 @@ -97,6 +97,57 @@ public void enableTorch_turnsTorchModeOnAndOffAsExpected() { } } + @Test + public void setZoomRatio_setsZoomAsExpected() { + try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) { + final CameraControlHostApiImpl cameraControlHostApiImpl = + new CameraControlHostApiImpl(testInstanceManager, mock(Context.class)); + final Long cameraControlIdentifier = 33L; + final Double zoomRatio = 0.2D; + + @SuppressWarnings("unchecked") + final ListenableFuture setZoomRatioFuture = mock(ListenableFuture.class); + + testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier); + + when(cameraControl.setZoomRatio(zoomRatio.floatValue())).thenReturn(setZoomRatioFuture); + + @SuppressWarnings("unchecked") + final ArgumentCaptor> futureCallbackCaptor = + ArgumentCaptor.forClass(FutureCallback.class); + + // Test successful behavior. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result successfulMockResult = + mock(GeneratedCameraXLibrary.Result.class); + cameraControlHostApiImpl.setZoomRatio( + cameraControlIdentifier, zoomRatio, successfulMockResult); + mockedFutures.verify( + () -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback successfulSetZoomRatioCallback = futureCallbackCaptor.getValue(); + + successfulSetZoomRatioCallback.onSuccess(mock(Void.class)); + verify(successfulMockResult).success(null); + + // Test failed behavior. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result failedMockResult = + mock(GeneratedCameraXLibrary.Result.class); + final Throwable testThrowable = new Throwable(); + cameraControlHostApiImpl.setZoomRatio(cameraControlIdentifier, zoomRatio, failedMockResult); + mockedFutures.verify( + () -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback failedSetZoomRatioCallback = futureCallbackCaptor.getValue(); + + failedSetZoomRatioCallback.onFailure(testThrowable); + verify(failedMockResult).error(testThrowable); + } + } + @Test public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() { final CameraControlFlutterApiImpl spyFlutterApi = diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart index ec6fb512eeb3..a4434e7d5a9c 100644 --- a/packages/camera/camera_android_camerax/example/lib/main.dart +++ b/packages/camera/camera_android_camerax/example/lib/main.dart @@ -392,12 +392,8 @@ class _CameraExampleHomeState extends State style: styleAuto, onPressed: () {}, // TODO(camsim99): Add functionality back here. - onLongPress: () { - if (controller != null) { - controller!.setExposurePoint(null); - showInSnackBar('Resetting exposure point'); - } - }, + onLongPress: + () {}, // TODO(camsim99): Add functionality back here., child: const Text('AUTO'), ), TextButton( @@ -470,12 +466,8 @@ class _CameraExampleHomeState extends State style: styleAuto, onPressed: () {}, // TODO(camsim99): Add functionality back here. - onLongPress: () { - if (controller != null) { - controller!.setFocusPoint(null); - } - showInSnackBar('Resetting focus point'); - }, + onLongPress: + () {}, // TODO(camsim99): Add functionality back here. child: const Text('AUTO'), ), TextButton( 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 8b06bf61a916..044778fc20f2 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 @@ -439,6 +439,17 @@ class AndroidCameraCameraX extends CameraPlatform { return zoomState.minZoomRatio; } + /// Set the zoom level for the selected camera. + /// + /// The supplied [zoom] value should be between the minimum and the maximum + /// supported zoom level returned by `getMinZoomLevel` and `getMaxZoomLevel`. + /// 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); + } + /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { @@ -846,7 +857,8 @@ class AndroidCameraCameraX extends CameraPlatform { /// closest lower resolution available. ResolutionSelector? _getResolutionSelectorFromPreset( ResolutionPreset? preset) { - const int fallbackRule = ResolutionStrategy.fallbackRuleClosestLower; + const int fallbackRule = + ResolutionStrategy.fallbackRuleClosestLowerThenHigher; Size? boundSize; ResolutionStrategy? resolutionStrategy; @@ -868,10 +880,14 @@ class AndroidCameraCameraX extends CameraPlatform { break; case ResolutionPreset.max: // Automatically set strategy to choose highest available. - resolutionStrategy = _shouldCreateDetachedObjectForTesting - ? ResolutionStrategy.detachedHighestAvailableStrategy() - : ResolutionStrategy.highestAvailableStrategy(); - break; + if (_shouldCreateDetachedObjectForTesting) { + resolutionStrategy = + ResolutionStrategy.detachedHighestAvailableStrategy(); + return ResolutionSelector.detached( + resolutionStrategy: resolutionStrategy); + } + resolutionStrategy = ResolutionStrategy.highestAvailableStrategy(); + return ResolutionSelector(resolutionStrategy: resolutionStrategy); case null: // If no preset is specified, default to CameraX's default behavior // for each UseCase. @@ -879,17 +895,15 @@ class AndroidCameraCameraX extends CameraPlatform { } if (_shouldCreateDetachedObjectForTesting) { - resolutionStrategy ??= ResolutionStrategy.detached( + resolutionStrategy = ResolutionStrategy.detached( boundSize: boundSize, fallbackRule: fallbackRule); return ResolutionSelector.detached( resolutionStrategy: resolutionStrategy); } - resolutionStrategy ??= - ResolutionStrategy(boundSize: boundSize!, fallbackRule: fallbackRule); - return ResolutionSelector( - resolutionStrategy: ResolutionStrategy( - boundSize: boundSize!, fallbackRule: fallbackRule)); + resolutionStrategy = + ResolutionStrategy(boundSize: boundSize, fallbackRule: fallbackRule); + return ResolutionSelector(resolutionStrategy: resolutionStrategy); } /// Returns the [QualitySelector] that maps to the specified resolution @@ -926,7 +940,7 @@ class AndroidCameraCameraX extends CameraPlatform { // We will choose the next highest video quality if the one desired // is unavailable. const VideoResolutionFallbackRule fallbackRule = - VideoResolutionFallbackRule.lowerQualityThan; + VideoResolutionFallbackRule.lowerQualityOrHigherThan; final FallbackStrategy fallbackStrategy = _shouldCreateDetachedObjectForTesting ? FallbackStrategy.detached( 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 b30195ce9286..57b84772be30 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_control.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_control.dart @@ -32,9 +32,22 @@ class CameraControl extends JavaObject { late final _CameraControlHostApiImpl _api; /// Enables or disables the torch of related [Camera] instance. + /// + /// If the torch mode was unable to be changed, an error message will be + /// added to [SystemServices.cameraErrorStreamController]. Future enableTorch(bool torch) async { return _api.enableTorchFromInstance(this, torch); } + + /// Sets zoom of related [Camera] by ratio. + /// + /// Ratio should be between what the `minZoomRatio` and `maxZoomRatio` of the + /// [ZoomState] of the [CameraInfo] instance that is retrievable from the same + /// [Camera] instance; otherwise, an error message will be added to + /// [SystemServices.cameraErrorStreamController]. + Future setZoomRatio(double ratio) async { + return _api.setZoomRatioFromInstance(this, ratio); + } } /// Host API implementation of [CameraControl]. @@ -69,6 +82,18 @@ class _CameraControlHostApiImpl extends CameraControlHostApi { .add(e.message ?? 'The camera was unable to change torch modes.'); } } + + /// Sets zoom of specified [CameraControl] instance by ratio. + Future setZoomRatioFromInstance( + CameraControl instance, double ratio) async { + final int identifier = instanceManager.getIdentifier(instance)!; + try { + await setZoomRatio(identifier, ratio); + } on PlatformException catch (e) { + SystemServices.cameraErrorStreamController.add(e.message ?? + 'Zoom ratio was unable to be set. If ratio was not out of range, newer value may have been set; otherwise, the camera may be closed.'); + } + } } /// Flutter API implementation of [CameraControl]. 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 4fc4d1573ef2..4dd5253bd2d6 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 @@ -2685,6 +2685,28 @@ class CameraControlHostApi { return; } } + + Future setZoomRatio(int arg_identifier, double arg_ratio) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraControlHostApi.setZoomRatio', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_identifier, arg_ratio]) 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; + } + } } abstract class CameraControlFlutterApi { diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 2c5b9c2a77c9..22827a803eb9 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -424,6 +424,9 @@ abstract class FallbackStrategyHostApi { abstract class CameraControlHostApi { @async void enableTorch(int identifier, bool torch); + + @async + void setZoomRatio(int identifier, double ratio); } @FlutterApi() diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 238d94c8c969..d7500c09d3ae 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+19 +version: 0.5.0+22 environment: sdk: ">=2.19.0 <4.0.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 10c8e5b87239..1b2b7c50a84f 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 @@ -342,7 +342,7 @@ void main() { // resolution. expectedResolutionStrategy ??= ResolutionStrategy.detached( boundSize: expectedBoundSize, - fallbackRule: ResolutionStrategy.fallbackRuleClosestLower); + fallbackRule: ResolutionStrategy.fallbackRuleClosestLowerThenHigher); expect(camera.preview!.resolutionSelector!.resolutionStrategy!.boundSize, equals(expectedResolutionStrategy.boundSize)); @@ -431,7 +431,7 @@ void main() { } const VideoResolutionFallbackRule expectedFallbackRule = - VideoResolutionFallbackRule.lowerQualityThan; + VideoResolutionFallbackRule.lowerQualityOrHigherThan; final FallbackStrategy expectedFallbackStrategy = FallbackStrategy.detached( quality: expectedVideoQuality, @@ -1219,6 +1219,22 @@ void main() { expect(await camera.getMinZoomLevel(55), minZoomRatio); }); + test('setZoomLevel sets zoom ratio as expected', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 44; + const double zoomRatio = 0.3; + final MockCameraControl mockCameraControl = MockCameraControl(); + + camera.camera = MockCamera(); + + when(camera.camera!.getCameraControl()) + .thenAnswer((_) async => mockCameraControl); + + await camera.setZoomLevel(cameraId, zoomRatio); + + verify(mockCameraControl.setZoomRatio(zoomRatio)); + }); + test( 'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller', () async { 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 f275ea7021a1..4fae31e06390 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 @@ -368,6 +368,15 @@ class MockCameraControl extends _i1.Mock implements _i3.CameraControl { returnValue: _i15.Future.value(), returnValueForMissingStub: _i15.Future.value(), ) as _i15.Future); + @override + _i15.Future setZoomRatio(double? ratio) => (super.noSuchMethod( + Invocation.method( + #setZoomRatio, + [ratio], + ), + returnValue: _i15.Future.value(), + returnValueForMissingStub: _i15.Future.value(), + ) as _i15.Future); } /// A class which mocks [CameraImageData]. 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 99acc94b4bc0..77a9e0c0327c 100644 --- a/packages/camera/camera_android_camerax/test/camera_control_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_control_test.dart @@ -47,6 +47,32 @@ void main() { verify(mockApi.enableTorch(cameraControlIdentifier, enableTorch)); }); + test('setZoomRatio makes call on Java side to set zoom ratio', () async { + final MockTestCameraControlHostApi mockApi = + MockTestCameraControlHostApi(); + TestCameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraControl cameraControl = CameraControl.detached( + instanceManager: instanceManager, + ); + const int cameraControlIdentifier = 45; + + instanceManager.addHostCreatedInstance( + cameraControl, + cameraControlIdentifier, + onCopy: (_) => CameraControl.detached(instanceManager: instanceManager), + ); + + const double zoom = 0.2; + await cameraControl.setZoomRatio(zoom); + + verify(mockApi.setZoomRatio(cameraControlIdentifier, zoom)); + }); + 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 0f43f0c4348f..519fc6236695 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 @@ -47,6 +47,22 @@ class MockTestCameraControlHostApi extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); + @override + _i3.Future setZoomRatio( + int? identifier, + double? ratio, + ) => + (super.noSuchMethod( + Invocation.method( + #setZoomRatio, + [ + identifier, + ratio, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } /// A class which mocks [TestInstanceManagerHostApi]. 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 88e44ae61195..4773effcb732 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 @@ -1753,6 +1753,8 @@ abstract class TestCameraControlHostApi { Future enableTorch(int identifier, bool torch); + Future setZoomRatio(int identifier, double ratio); + static void setup(TestCameraControlHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1780,5 +1782,30 @@ abstract class TestCameraControlHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraControlHostApi.setZoomRatio', 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.CameraControlHostApi.setZoomRatio 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.CameraControlHostApi.setZoomRatio was null, expected non-null int.'); + final double? arg_ratio = (args[1] as double?); + assert(arg_ratio != null, + 'Argument for dev.flutter.pigeon.CameraControlHostApi.setZoomRatio was null, expected non-null double.'); + await api.setZoomRatio(arg_identifier!, arg_ratio!); + return []; + }); + } + } } } diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 8f221842e400..cac28692b704 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.13+7 + +* Fixes inverted orientation strings. + ## 0.9.13+6 * Fixes incorrect use of `NSError` that could cause crashes on launch. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m index 60e88fffee2b..f63c3fb89576 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m @@ -28,11 +28,11 @@ - (void)testOrientationNotifications { expectedChannelOrientation:@"portraitUp" cameraPlugin:cameraPlugin messenger:mockMessenger]; - [self rotate:UIDeviceOrientationLandscapeRight + [self rotate:UIDeviceOrientationLandscapeLeft expectedChannelOrientation:@"landscapeLeft" cameraPlugin:cameraPlugin messenger:mockMessenger]; - [self rotate:UIDeviceOrientationLandscapeLeft + [self rotate:UIDeviceOrientationLandscapeRight expectedChannelOrientation:@"landscapeRight" cameraPlugin:cameraPlugin messenger:mockMessenger]; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m index 95203bfa9e90..c429d2d9751d 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m @@ -84,9 +84,9 @@ - (void)testFLTGetVideoFormatFromString { - (void)testFLTGetUIDeviceOrientationForString { XCTAssertEqual(UIDeviceOrientationPortraitUpsideDown, FLTGetUIDeviceOrientationForString(@"portraitDown")); - XCTAssertEqual(UIDeviceOrientationLandscapeRight, - FLTGetUIDeviceOrientationForString(@"landscapeLeft")); XCTAssertEqual(UIDeviceOrientationLandscapeLeft, + FLTGetUIDeviceOrientationForString(@"landscapeLeft")); + XCTAssertEqual(UIDeviceOrientationLandscapeRight, FLTGetUIDeviceOrientationForString(@"landscapeRight")); XCTAssertEqual(UIDeviceOrientationPortrait, FLTGetUIDeviceOrientationForString(@"portraitUp")); XCTAssertEqual(UIDeviceOrientationUnknown, FLTGetUIDeviceOrientationForString(@"unknown")); @@ -96,9 +96,9 @@ - (void)testFLTGetStringForUIDeviceOrientation { XCTAssertEqualObjects(@"portraitDown", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortraitUpsideDown)); XCTAssertEqualObjects(@"landscapeLeft", - FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeRight)); - XCTAssertEqualObjects(@"landscapeRight", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeLeft)); + XCTAssertEqualObjects(@"landscapeRight", + FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeRight)); XCTAssertEqualObjects(@"portraitUp", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortrait)); XCTAssertEqualObjects(@"portraitUp", FLTGetStringForUIDeviceOrientation(-1)); diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m index 147d3e3670bb..69daa515e1a9 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m @@ -90,9 +90,9 @@ UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation) { if ([orientation isEqualToString:@"portraitDown"]) { return UIDeviceOrientationPortraitUpsideDown; } else if ([orientation isEqualToString:@"landscapeLeft"]) { - return UIDeviceOrientationLandscapeRight; - } else if ([orientation isEqualToString:@"landscapeRight"]) { return UIDeviceOrientationLandscapeLeft; + } else if ([orientation isEqualToString:@"landscapeRight"]) { + return UIDeviceOrientationLandscapeRight; } else if ([orientation isEqualToString:@"portraitUp"]) { return UIDeviceOrientationPortrait; } else { @@ -104,9 +104,9 @@ UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation) { switch (orientation) { case UIDeviceOrientationPortraitUpsideDown: return @"portraitDown"; - case UIDeviceOrientationLandscapeRight: - return @"landscapeLeft"; case UIDeviceOrientationLandscapeLeft: + return @"landscapeLeft"; + case UIDeviceOrientationLandscapeRight: return @"landscapeRight"; case UIDeviceOrientationPortrait: default: diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index b4a4396926d8..3de45b68908e 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ 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.13+6 +version: 0.9.13+7 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 7b695625fa7d..05ead93a81b2 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0+4 + +* Updates annotations lib to 1.7.0. + ## 0.5.0+3 * Adds pub topics to package metadata. diff --git a/packages/file_selector/file_selector_android/android/build.gradle b/packages/file_selector/file_selector_android/android/build.gradle index 16aaa5d4f4d3..27c4ac6e926f 100644 --- a/packages/file_selector/file_selector_android/android/build.gradle +++ b/packages/file_selector/file_selector_android/android/build.gradle @@ -38,7 +38,7 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.6.0' + implementation 'androidx.annotation:annotation:1.7.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'androidx.test:core:1.3.0' diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 37d4f21085b8..94732beb9961 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_android description: Android implementation of the file_selector package. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.0+3 +version: 0.5.0+4 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index f09aba9966cd..d66052a6527c 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.1+7 + +* Updates to Pigeon 13. + ## 0.5.1+6 * Adds pub topics to package metadata. diff --git a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m index c52aad59f585..521beb7bc578 100644 --- a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m +++ b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m @@ -23,8 +23,7 @@ - (void)testPickerPresents { plugin.documentPickerViewControllerOverride = picker; plugin.presentingViewControllerOverride = mockPresentingVC; - [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] - allowMultiSelection:@NO] + [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:NO] completion:^(NSArray *paths, FlutterError *error){ }]; @@ -42,7 +41,7 @@ - (void)testReturnsPickedFiles { inMode:UIDocumentPickerModeImport]; plugin.documentPickerViewControllerOverride = picker; [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] - allowMultiSelection:@YES] + allowMultiSelection:YES] completion:^(NSArray *paths, FlutterError *error) { NSArray *expectedPaths = @[ @"/file1.txt", @"/file2.txt" ]; XCTAssertEqualObjects(paths, expectedPaths); @@ -63,8 +62,7 @@ - (void)testCancellingPickerReturnsNil { plugin.documentPickerViewControllerOverride = picker; XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; - [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] - allowMultiSelection:@NO] + [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:NO] completion:^(NSArray *paths, FlutterError *error) { XCTAssertEqual(paths.count, 0); [completionWasCalled fulfill]; diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m index 8a92cc3131c5..ee9b840d5c48 100644 --- a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m +++ b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m @@ -21,7 +21,7 @@ - (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config initWithDocumentTypes:config.utis inMode:UIDocumentPickerModeImport]; documentPicker.delegate = self; - documentPicker.allowsMultipleSelection = config.allowMultiSelection.boolValue; + documentPicker.allowsMultipleSelection = config.allowMultiSelection; UIViewController *presentingVC = self.presentingViewControllerOverride @@ -41,7 +41,7 @@ - (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config + (void)registerWithRegistrar:(NSObject *)registrar { FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; - FFSFileSelectorApiSetup(registrar.messenger, plugin); + SetUpFFSFileSelectorApi(registrar.messenger, plugin); } #pragma mark - UIDocumentPickerDelegate diff --git a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.h b/packages/file_selector/file_selector_ios/ios/Classes/messages.g.h index bbaca5b5df67..d0f9d977f4b5 100644 --- a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.h +++ b/packages/file_selector/file_selector_ios/ios/Classes/messages.g.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 (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -19,9 +19,9 @@ NS_ASSUME_NONNULL_BEGIN /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithUtis:(NSArray *)utis - allowMultiSelection:(NSNumber *)allowMultiSelection; -@property(nonatomic, strong) NSArray *utis; -@property(nonatomic, strong) NSNumber *allowMultiSelection; + allowMultiSelection:(BOOL)allowMultiSelection; +@property(nonatomic, copy) NSArray *utis; +@property(nonatomic, assign) BOOL allowMultiSelection; @end /// The codec used by FFSFileSelectorApi. @@ -33,7 +33,7 @@ NSObject *FFSFileSelectorApiGetCodec(void); FlutterError *_Nullable))completion; @end -extern void FFSFileSelectorApiSetup(id binaryMessenger, +extern void SetUpFFSFileSelectorApi(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END diff --git a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.m b/packages/file_selector/file_selector_ios/ios/Classes/messages.g.m index 8a4ee261bd77..1905261653ba 100644 --- a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.m +++ b/packages/file_selector/file_selector_ios/ios/Classes/messages.g.m @@ -1,11 +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. -// Autogenerated from Pigeon (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else #import +#endif #if !__has_feature(objc_arc) #error File requires ARC to be enabled. @@ -32,7 +37,7 @@ - (NSArray *)toList; @implementation FFSFileSelectorConfig + (instancetype)makeWithUtis:(NSArray *)utis - allowMultiSelection:(NSNumber *)allowMultiSelection { + allowMultiSelection:(BOOL)allowMultiSelection { FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; pigeonResult.utis = utis; pigeonResult.allowMultiSelection = allowMultiSelection; @@ -41,9 +46,7 @@ + (instancetype)makeWithUtis:(NSArray *)utis + (FFSFileSelectorConfig *)fromList:(NSArray *)list { FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; pigeonResult.utis = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.utis != nil, @""); - pigeonResult.allowMultiSelection = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.allowMultiSelection != nil, @""); + pigeonResult.allowMultiSelection = [GetNullableObjectAtIndex(list, 1) boolValue]; return pigeonResult; } + (nullable FFSFileSelectorConfig *)nullableFromList:(NSArray *)list { @@ -51,8 +54,8 @@ + (nullable FFSFileSelectorConfig *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.utis ?: [NSNull null]), - (self.allowMultiSelection ?: [NSNull null]), + self.utis ?: [NSNull null], + @(self.allowMultiSelection), ]; } @end @@ -105,11 +108,11 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FFSFileSelectorApiSetup(id binaryMessenger, +void SetUpFFSFileSelectorApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.FileSelectorApi.openFile" + initWithName:@"dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile" binaryMessenger:binaryMessenger codec:FFSFileSelectorApiGetCodec()]; if (api) { diff --git a/packages/file_selector/file_selector_ios/lib/src/messages.g.dart b/packages/file_selector/file_selector_ios/lib/src/messages.g.dart index c623de74dc49..6ec723c8b70f 100644 --- a/packages/file_selector/file_selector_ios/lib/src/messages.g.dart +++ b/packages/file_selector/file_selector_ios/lib/src/messages.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 (v9.2.4), 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,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + class FileSelectorConfig { FileSelectorConfig({ required this.utis, @@ -72,7 +83,7 @@ class FileSelectorApi { Future> openFile(FileSelectorConfig arg_config) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.FileSelectorApi.openFile', codec, + 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_config]) as List?; diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index af8591c0a9d2..58c0def07209 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.1+6 +version: 0.5.1+7 environment: sdk: ">=2.19.0 <4.0.0" @@ -26,7 +26,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^9.2.4 + pigeon: ^13.0.0 topics: - files diff --git a/packages/file_selector/file_selector_ios/test/test_api.g.dart b/packages/file_selector/file_selector_ios/test/test_api.g.dart index 5648f26bdeee..7b0ace73ef9e 100644 --- a/packages/file_selector/file_selector_ios/test/test_api.g.dart +++ b/packages/file_selector/file_selector_ios/test/test_api.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 (v9.2.4), 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 @@ -47,7 +47,8 @@ abstract class TestFileSelectorApi { {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.FileSelectorApi.openFile', codec, + 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile', + codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -57,14 +58,21 @@ abstract class TestFileSelectorApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.FileSelectorApi.openFile was null.'); + 'Argument for dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile was null.'); final List args = (message as List?)!; final FileSelectorConfig? arg_config = (args[0] as FileSelectorConfig?); assert(arg_config != null, - 'Argument for dev.flutter.pigeon.FileSelectorApi.openFile was null, expected non-null FileSelectorConfig.'); - final List output = await api.openFile(arg_config!); - return [output]; + 'Argument for dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile was null, expected non-null FileSelectorConfig.'); + try { + final List output = await api.openFile(arg_config!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index a1cd3326434a..bce44052293d 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -223,7 +223,7 @@ class NetworkImageWithRetry extends ImageProvider { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } diff --git a/packages/flutter_lints/CHANGELOG.md b/packages/flutter_lints/CHANGELOG.md index 065b9d3d986b..0d2507a2677e 100644 --- a/packages/flutter_lints/CHANGELOG.md +++ b/packages/flutter_lints/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.1 + +* Replaces `flutter pub add --dev` with `dev:` in README.md. + ## 3.0.0 * Updated `package:lints` dependency to version 3.0.0, with the following changes: diff --git a/packages/flutter_lints/README.md b/packages/flutter_lints/README.md index f69eb505f905..941fb47e7452 100644 --- a/packages/flutter_lints/README.md +++ b/packages/flutter_lints/README.md @@ -19,7 +19,7 @@ package. Entities created before that version can use these lints by following these instructions: 1. Depend on this package as a **dev_dependency** by running - `flutter pub add --dev flutter_lints`. + `flutter pub add dev:flutter_lints`. 2. Create an `analysis_options.yaml` file at the root of the package (alongside the `pubspec.yaml` file) and `include: package:flutter_lints/flutter.yaml` from it. diff --git a/packages/flutter_lints/pubspec.yaml b/packages/flutter_lints/pubspec.yaml index 576a3d71194d..d081e4b736f8 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.0 +version: 3.0.1 environment: sdk: ^3.0.0 diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index dcf09eafb592..dd37dea0bef6 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.18+2 + +* Removes leading whitespace from list items. + ## 0.6.18+1 * Fixes a typo in README. diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index 07cde26ad8b9..0ab3346d3376 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -323,7 +323,8 @@ class MarkdownBuilder implements md.NodeVisitor { // Leading spaces in paragraph or list item are ignored // https://github.github.com/gfm/#example-192 // https://github.github.com/gfm/#example-236 - if (const ['ul', 'ol', 'p', 'br'].contains(_lastVisitedTag)) { + if (const ['ul', 'ol', 'li', 'p', 'br'] + .contains(_lastVisitedTag)) { text = text.replaceAll(leadingSpacesPattern, ''); } diff --git a/packages/flutter_markdown/lib/src/style_sheet.dart b/packages/flutter_markdown/lib/src/style_sheet.dart index 7b84b6f6cadc..8d73d318aa9c 100644 --- a/packages/flutter_markdown/lib/src/style_sheet.dart +++ b/packages/flutter_markdown/lib/src/style_sheet.dart @@ -659,7 +659,7 @@ class MarkdownStyleSheet { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index b2396c2f2d9b..cb14d71d16a7 100644 --- a/packages/flutter_markdown/pubspec.yaml +++ b/packages/flutter_markdown/pubspec.yaml @@ -4,7 +4,7 @@ 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.18+1 +version: 0.6.18+2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/flutter_markdown/test/list_test.dart b/packages/flutter_markdown/test/list_test.dart index 9e2b1fcab448..05464e7b93fc 100644 --- a/packages/flutter_markdown/test/list_test.dart +++ b/packages/flutter_markdown/test/list_test.dart @@ -72,6 +72,28 @@ void defineTests() { 'two', ]); }); + + testWidgets( + 'leading spaces are ignored (non-paragraph test case)', + (WidgetTester tester) async { + const String data = '- one\n- two\n- three'; + await tester.pumpWidget( + boilerplate( + const MarkdownBody(data: data), + ), + ); + + final Iterable widgets = tester.allWidgets; + expectTextStrings(widgets, [ + '•', + 'one', + '•', + 'two', + '•', + 'three', + ]); + }, + ); }); group('Ordered List', () { diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md index 6461d7b769d8..f5bb639c16fa 100644 --- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.17 + +* Updates annotations lib to 1.7.0. + ## 2.0.16 * Adds pub topics to package metadata. diff --git a/packages/flutter_plugin_android_lifecycle/android/build.gradle b/packages/flutter_plugin_android_lifecycle/android/build.gradle index 9ec027f33655..44194833c94e 100644 --- a/packages/flutter_plugin_android_lifecycle/android/build.gradle +++ b/packages/flutter_plugin_android_lifecycle/android/build.gradle @@ -46,7 +46,7 @@ android { } dependencies { - implementation "androidx.annotation:annotation:1.5.0" + implementation "androidx.annotation:annotation:1.7.0" } diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml index d22f9bab8b5a..7a7975d267be 100644 --- a/packages/flutter_plugin_android_lifecycle/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_plugin_android_lifecycle description: Flutter plugin for accessing an Android Lifecycle within other plugins. repository: https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_plugin_android_lifecycle%22 -version: 2.0.16 +version: 2.0.17 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 3c9ce2c40b33..63e9cdbf4ba9 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,19 @@ +## 12.1.0 + +- Adds an ability to add a custom codec for serializing/deserializing extra. + +## 12.0.3 + +- Fixes crashes when dynamically updates routing tables with named routes. + +## 12.0.2 + +- Fixes the problem that pathParameters is null in redirect when the Router is recreated. + +## 12.0.1 + +- Fixes deep-link with no path on cold start. + ## 12.0.0 - Adds ability to dynamically update routing table. diff --git a/packages/go_router/doc/navigation.md b/packages/go_router/doc/navigation.md index f5fa16ac4baf..f351d2b821d4 100644 --- a/packages/go_router/doc/navigation.md +++ b/packages/go_router/doc/navigation.md @@ -84,5 +84,25 @@ Returning a value: onTap: () => context.pop(true) ``` +## Using extra +You can provide additional data along with navigation. + +```dart +context.go('/123, extra: 'abc'); +``` + +and retrieve the data from GoRouterState + +```dart +final String extraString = GoRouterState.of(context).extra! as String; +``` + +The extra data will go through serialization when it is stored in the browser. +If you plan to use complex data as extra, consider also providing a codec +to GoRouter so that it won't get dropped during serialization. + +For an example on how to use complex data in extra with a codec, see +[extra_codec.dart](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/extra_codec.dart). + [Named routes]: https://pub.dev/documentation/go_router/latest/topics/Named%20routes-topic.html diff --git a/packages/go_router/example/README.md b/packages/go_router/example/README.md index 55f75bca6a6a..cac540fa0f87 100644 --- a/packages/go_router/example/README.md +++ b/packages/go_router/example/README.md @@ -41,6 +41,11 @@ An example to demonstrate how to use a `StatefulShellRoute` to create stateful n An example to demonstrate how to handle exception in go_router. +## [Extra Codec](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/extra_codec.dart) +`flutter run lib/extra_codec.dart` + +An example to demonstrate how to use a complex object as extra. + ## [Books app](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/books) `flutter run lib/books/main.dart` diff --git a/packages/go_router/example/ios/Runner.xcodeproj/project.pbxproj b/packages/go_router/example/ios/Runner.xcodeproj/project.pbxproj index 0841413a1fd2..8f3ef0d66bf0 100644 --- a/packages/go_router/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/go_router/example/ios/Runner.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/go_router/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/go_router/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e1fb7..b52b2e698b7e 100644 --- a/packages/go_router/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/go_router/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ runApp(const MyApp()); + +/// The router configuration. +final GoRouter _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) => + const HomeScreen(), + ), + ], + extraCodec: const MyExtraCodec(), +); + +/// The main app. +class MyApp extends StatelessWidget { + /// Constructs a [MyApp] + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: _router, + ); + } +} + +/// The home screen. +class HomeScreen extends StatelessWidget { + /// Constructs a [HomeScreen]. + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home Screen')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "If running in web, use the browser's backward and forward button to test extra codec after setting extra several times."), + Text( + 'The extra for this page is: ${GoRouterState.of(context).extra}'), + ElevatedButton( + onPressed: () => context.go('/', extra: ComplexData1('data')), + child: const Text('Set extra to ComplexData1'), + ), + ElevatedButton( + onPressed: () => context.go('/', extra: ComplexData2('data')), + child: const Text('Set extra to ComplexData2'), + ), + ], + ), + ), + ); + } +} + +/// A complex class. +class ComplexData1 { + /// Create a complex object. + ComplexData1(this.data); + + /// The data. + final String data; + + @override + String toString() => 'ComplexData1(data: $data)'; +} + +/// A complex class. +class ComplexData2 { + /// Create a complex object. + ComplexData2(this.data); + + /// The data. + final String data; + + @override + String toString() => 'ComplexData2(data: $data)'; +} + +/// A codec that can serialize both [ComplexData1] and [ComplexData2]. +class MyExtraCodec extends Codec { + /// Create a codec. + const MyExtraCodec(); + @override + Converter get decoder => const _MyExtraDecoder(); + + @override + Converter get encoder => const _MyExtraEncoder(); +} + +class _MyExtraDecoder extends Converter { + const _MyExtraDecoder(); + @override + Object? convert(Object? input) { + if (input == null) { + return null; + } + final List inputAsList = input as List; + if (inputAsList[0] == 'ComplexData1') { + return ComplexData1(inputAsList[1]! as String); + } + if (inputAsList[0] == 'ComplexData2') { + return ComplexData2(inputAsList[1]! as String); + } + throw FormatException('Unable tp parse input: $input'); + } +} + +class _MyExtraEncoder extends Converter { + const _MyExtraEncoder(); + @override + Object? convert(Object? input) { + if (input == null) { + return null; + } + switch (input.runtimeType) { + case ComplexData1: + return ['ComplexData1', (input as ComplexData1).data]; + case ComplexData2: + return ['ComplexData2', (input as ComplexData2).data]; + default: + throw FormatException('Cannot encode type ${input.runtimeType}'); + } + } +} diff --git a/packages/go_router/example/lib/routing_config.dart b/packages/go_router/example/lib/routing_config.dart index 34255208d1e0..85acf20c9181 100644 --- a/packages/go_router/example/lib/routing_config.dart +++ b/packages/go_router/example/lib/routing_config.dart @@ -48,7 +48,7 @@ class _MyAppState extends State { return Scaffold( appBar: AppBar(title: const Text('Home')), body: Center( - child: Row( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( diff --git a/packages/go_router/example/test/extra_codec_test.dart b/packages/go_router/example/test/extra_codec_test.dart new file mode 100644 index 000000000000..7358cfc614ed --- /dev/null +++ b/packages/go_router/example/test/extra_codec_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_test/flutter_test.dart'; +import 'package:go_router_examples/extra_codec.dart' as example; + +void main() { + testWidgets('example works', (WidgetTester tester) async { + await tester.pumpWidget(const example.MyApp()); + expect(find.text('The extra for this page is: null'), findsOneWidget); + + await tester.tap(find.text('Set extra to ComplexData1')); + await tester.pumpAndSettle(); + expect(find.text('The extra for this page is: ComplexData1(data: data)'), + findsOneWidget); + + await tester.tap(find.text('Set extra to ComplexData2')); + await tester.pumpAndSettle(); + expect(find.text('The extra for this page is: ComplexData2(data: data)'), + findsOneWidget); + }); +} diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index 8e6393503a5b..5149d18e8520 100644 --- a/packages/go_router/lib/src/configuration.dart +++ b/packages/go_router/lib/src/configuration.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -25,6 +26,7 @@ class RouteConfiguration { RouteConfiguration( this._routingConfig, { required this.navigatorKey, + this.extraCodec, }) { _onRoutingTableChanged(); _routingConfig.addListener(_onRoutingTableChanged); @@ -196,6 +198,7 @@ class RouteConfiguration { assert(_debugCheckParentNavigatorKeys( routingTable.routes, >[navigatorKey])); assert(_debugCheckStatefulShellBranchDefaultLocations(routingTable.routes)); + _nameToPath.clear(); _cacheNameToPath('', routingTable.routes); log(debugKnownRoutes()); } @@ -231,6 +234,19 @@ class RouteConfiguration { /// The global key for top level navigator. final GlobalKey navigatorKey; + /// The codec used to encode and decode extra into a serializable format. + /// + /// When navigating using [GoRouter.go] or [GoRouter.push], one can provide + /// an `extra` parameter along with it. If the extra contains complex data, + /// consider provide a codec for serializing and deserializing the extra data. + /// + /// See also: + /// * [Navigation](https://pub.dev/documentation/go_router/latest/topics/Navigation-topic.html) + /// topic. + /// * [extra_codec](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/extra_codec.dart) + /// example. + final Codec? extraCodec; + final Map _nameToPath = {}; /// Looks up the url location by a [GoRoute]'s name. @@ -495,17 +511,19 @@ class RouteConfiguration { final RouteBase route = match.route; FutureOr routeRedirectResult; if (route is GoRoute && route.redirect != null) { + final RouteMatchList effectiveMatchList = + match is ImperativeRouteMatch ? match.matches : matchList; routeRedirectResult = route.redirect!( context, GoRouterState( this, - uri: matchList.uri, + uri: effectiveMatchList.uri, matchedLocation: match.matchedLocation, name: route.name, path: route.path, - fullPath: matchList.fullPath, - extra: matchList.extra, - pathParameters: matchList.pathParameters, + fullPath: effectiveMatchList.fullPath, + extra: effectiveMatchList.extra, + pathParameters: effectiveMatchList.pathParameters, pageKey: match.pageKey, ), ); diff --git a/packages/go_router/lib/src/logging.dart b/packages/go_router/lib/src/logging.dart index 3f39e4dc804d..7f0a8ce5a7a9 100644 --- a/packages/go_router/lib/src/logging.dart +++ b/packages/go_router/lib/src/logging.dart @@ -16,9 +16,9 @@ final Logger logger = Logger('GoRouter'); bool _enabled = false; /// Logs the message if logging is enabled. -void log(String message) { +void log(String message, {Level level = Level.INFO}) { if (_enabled) { - logger.info(message); + logger.log(level, message); } } diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index e5ffdec31d70..3b7a94688692 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -8,9 +8,11 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'configuration.dart'; +import 'logging.dart'; import 'misc/errors.dart'; import 'path_utils.dart'; import 'route.dart'; @@ -358,21 +360,25 @@ class RouteMatchList { /// Handles encoding and decoding of [RouteMatchList] objects to a format /// suitable for using with [StandardMessageCodec]. /// -/// The primary use of this class is for state restoration. +/// The primary use of this class is for state restoration and browser history. @internal class RouteMatchListCodec extends Codec> { /// Creates a new [RouteMatchListCodec] object. RouteMatchListCodec(RouteConfiguration configuration) - : decoder = _RouteMatchListDecoder(configuration); + : decoder = _RouteMatchListDecoder(configuration), + encoder = _RouteMatchListEncoder(configuration); static const String _locationKey = 'location'; static const String _extraKey = 'state'; static const String _imperativeMatchesKey = 'imperativeMatches'; static const String _pageKey = 'pageKey'; + static const String _codecKey = 'codec'; + static const String _jsonCodecName = 'json'; + static const String _customCodecName = 'custom'; + static const String _encodedKey = 'encoded'; @override - final Converter> encoder = - const _RouteMatchListEncoder(); + final Converter> encoder; @override final Converter, RouteMatchList> decoder; @@ -380,7 +386,9 @@ class RouteMatchListCodec extends Codec> { class _RouteMatchListEncoder extends Converter> { - const _RouteMatchListEncoder(); + const _RouteMatchListEncoder(this.configuration); + + final RouteConfiguration configuration; @override Map convert(RouteMatchList input) { final List> imperativeMatches = input.matches @@ -394,15 +402,36 @@ class _RouteMatchListEncoder imperativeMatches: imperativeMatches); } - static Map _toPrimitives(String location, Object? extra, + Map _toPrimitives(String location, Object? extra, {List>? imperativeMatches, String? pageKey}) { - String? encodedExtra; - try { - encodedExtra = json.encoder.convert(extra); - } on JsonUnsupportedObjectError {/* give up if not serializable */} + Map encodedExtra; + if (configuration.extraCodec != null) { + encodedExtra = { + RouteMatchListCodec._codecKey: RouteMatchListCodec._customCodecName, + RouteMatchListCodec._encodedKey: + configuration.extraCodec?.encode(extra), + }; + } else { + String jsonEncodedExtra; + try { + jsonEncodedExtra = json.encoder.convert(extra); + } on JsonUnsupportedObjectError { + jsonEncodedExtra = json.encoder.convert(null); + log( + 'An extra with complex data type ${extra.runtimeType} is provided ' + 'without a codec. Consider provide a codec to GoRouter to ' + 'prevent extra being dropped during serialization.', + level: Level.WARNING); + } + encodedExtra = { + RouteMatchListCodec._codecKey: RouteMatchListCodec._jsonCodecName, + RouteMatchListCodec._encodedKey: jsonEncodedExtra, + }; + } + return { RouteMatchListCodec._locationKey: location, - if (encodedExtra != null) RouteMatchListCodec._extraKey: encodedExtra, + RouteMatchListCodec._extraKey: encodedExtra, if (imperativeMatches != null) RouteMatchListCodec._imperativeMatchesKey: imperativeMatches, if (pageKey != null) RouteMatchListCodec._pageKey: pageKey, @@ -420,13 +449,17 @@ class _RouteMatchListDecoder RouteMatchList convert(Map input) { final String rootLocation = input[RouteMatchListCodec._locationKey]! as String; - final String? encodedExtra = - input[RouteMatchListCodec._extraKey] as String?; + final Map encodedExtra = + input[RouteMatchListCodec._extraKey]! as Map; final Object? extra; - if (encodedExtra != null) { - extra = json.decoder.convert(encodedExtra); + + if (encodedExtra[RouteMatchListCodec._codecKey] == + RouteMatchListCodec._jsonCodecName) { + extra = json.decoder + .convert(encodedExtra[RouteMatchListCodec._encodedKey]! as String); } else { - extra = null; + extra = configuration.extraCodec + ?.decode(encodedExtra[RouteMatchListCodec._encodedKey]); } RouteMatchList matchList = configuration.findMatch(rootLocation, extra: extra); diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 27485f088370..dc70baa16b2b 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -83,6 +83,9 @@ class GoRouteInformationParser extends RouteInformationParser { initialMatches = // TODO(chunhtai): remove this ignore and migrate the code // https://github.com/flutter/flutter/issues/124045. + // TODO(chunhtai): After the migration from routeInformation's location + // to uri, empty path check might be required here; see + // https://github.com/flutter/packages/pull/5113#discussion_r1374861070 // ignore: deprecated_member_use, unnecessary_non_null_assertion configuration.findMatch(routeInformation.location!, extra: state.extra); if (initialMatches.isError) { diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 2e90474be89b..0f27dcf48c40 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -121,6 +122,7 @@ class GoRouter implements RouterConfig { /// The `routes` must not be null and must contain an [GoRouter] to match `/`. factory GoRouter({ required List routes, + Codec? extraCodec, GoExceptionHandler? onException, GoRouterPageBuilder? errorPageBuilder, GoRouterWidgetBuilder? errorBuilder, @@ -144,6 +146,7 @@ class GoRouter implements RouterConfig { redirect: redirect ?? RoutingConfig._defaultRedirect, redirectLimit: redirectLimit), ), + extraCodec: extraCodec, onException: onException, errorPageBuilder: errorPageBuilder, errorBuilder: errorBuilder, @@ -165,6 +168,7 @@ class GoRouter implements RouterConfig { /// See [routing_config.dart](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/routing_config.dart). GoRouter.routingConfig({ required ValueListenable routingConfig, + Codec? extraCodec, GoExceptionHandler? onException, GoRouterPageBuilder? errorPageBuilder, GoRouterWidgetBuilder? errorBuilder, @@ -201,6 +205,7 @@ class GoRouter implements RouterConfig { configuration = RouteConfiguration( _routingConfig, navigatorKey: navigatorKey, + extraCodec: extraCodec, ); final ParserExceptionHandler? parserExceptionHandler; @@ -526,8 +531,10 @@ class GoRouter implements RouterConfig { // verified by assert() during the initialization. return initialLocation!; } + final Uri platformDefaultUri = + Uri.parse(WidgetsBinding.instance.platformDispatcher.defaultRouteName); final String platformDefault = - WidgetsBinding.instance.platformDispatcher.defaultRouteName; + platformDefaultUri.path.isEmpty ? '/' : platformDefaultUri.path; if (initialLocation == null) { return platformDefault; } else if (platformDefault == '/') { diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index b7c93c1ad889..f37ba325b304 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 12.0.0 +version: 12.1.0 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 diff --git a/packages/go_router/test/extra_codec_test.dart b/packages/go_router/test/extra_codec_test.dart new file mode 100644 index 000000000000..3c858cad17d8 --- /dev/null +++ b/packages/go_router/test/extra_codec_test.dart @@ -0,0 +1,111 @@ +// 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:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; + +import 'test_helpers.dart'; + +void main() { + testWidgets('router rebuild with extra codec works', + (WidgetTester tester) async { + const String initialString = 'some string'; + const String empty = 'empty'; + final GoRouter router = GoRouter( + initialLocation: '/', + extraCodec: ComplexDataCodec(), + initialExtra: ComplexData(initialString), + routes: [ + GoRoute( + path: '/', + builder: (_, GoRouterState state) { + return Text((state.extra as ComplexData?)?.data ?? empty); + }), + ], + redirect: (BuildContext context, _) { + // Set up dependency. + SimpleDependencyProvider.of(context); + return null; + }, + ); + final SimpleDependency dependency = SimpleDependency(); + addTearDown(() => dependency.dispose()); + + await tester.pumpWidget( + SimpleDependencyProvider( + dependency: dependency, + child: MaterialApp.router( + routerConfig: router, + ), + ), + ); + expect(find.text(initialString), findsOneWidget); + dependency.boolProperty = !dependency.boolProperty; + + await tester.pumpAndSettle(); + expect(find.text(initialString), findsOneWidget); + }); + + testWidgets('Restores state correctly', (WidgetTester tester) async { + const String initialString = 'some string'; + const String empty = 'empty'; + final List routes = [ + GoRoute( + path: '/', + builder: (_, GoRouterState state) { + return Text((state.extra as ComplexData?)?.data ?? empty); + }, + ), + ]; + + await createRouter( + routes, + tester, + initialExtra: ComplexData(initialString), + restorationScopeId: 'test', + extraCodec: ComplexDataCodec(), + ); + expect(find.text(initialString), findsOneWidget); + + await tester.restartAndRestore(); + + await tester.pumpAndSettle(); + expect(find.text(initialString), findsOneWidget); + }); +} + +class ComplexData { + ComplexData(this.data); + final String data; +} + +class ComplexDataCodec extends Codec { + @override + Converter get decoder => ComplexDataDecoder(); + @override + Converter get encoder => ComplexDataEncoder(); +} + +class ComplexDataDecoder extends Converter { + @override + ComplexData? convert(Object? input) { + if (input == null) { + return null; + } + return ComplexData(input as String); + } +} + +class ComplexDataEncoder extends Converter { + @override + Object? convert(ComplexData? input) { + if (input == null) { + return null; + } + return input.data; + } +} diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index e486129b201f..213b27ab60e9 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -2445,8 +2445,8 @@ void main() { // https://github.com/flutter/flutter/issues/124045. // ignore: deprecated_member_use expect(router.routeInformationProvider.value.location, '/dummy'); - TestWidgetsFlutterBinding - .instance.platformDispatcher.defaultRouteNameTestValue = '/'; + TestWidgetsFlutterBinding.instance.platformDispatcher + .clearDefaultRouteNameTestValue(); }); test('throws assertion if initialExtra is set w/o initialLocation', () { @@ -2466,6 +2466,45 @@ void main() { }); }); + group('_effectiveInitialLocation()', () { + final List routes = [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) => + const HomeScreen(), + ), + ]; + + testWidgets( + 'When platformDispatcher.defaultRouteName is deep-link Uri with ' + 'scheme, authority, no path', (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher + .defaultRouteNameTestValue = 'https://domain.com'; + final GoRouter router = await createRouter( + routes, + tester, + ); + expect(router.routeInformationProvider.value.uri.path, '/'); + TestWidgetsFlutterBinding.instance.platformDispatcher + .clearDefaultRouteNameTestValue(); + }); + + testWidgets( + 'When platformDispatcher.defaultRouteName is deep-link Uri with ' + 'scheme, authority, no path, but trailing slash', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher + .defaultRouteNameTestValue = 'https://domain.com/'; + final GoRouter router = await createRouter( + routes, + tester, + ); + expect(router.routeInformationProvider.value.uri.path, '/'); + TestWidgetsFlutterBinding.instance.platformDispatcher + .clearDefaultRouteNameTestValue(); + }); + }); + group('params', () { testWidgets('preserve path param case', (WidgetTester tester) async { final List routes = [ @@ -5009,6 +5048,44 @@ void main() { expectedInitialRoute); }); }); + + testWidgets( + 'test the pathParameters in redirect when the Router is recreated', + (WidgetTester tester) async { + final GoRouter router = GoRouter( + initialLocation: '/foo', + routes: [ + GoRoute( + path: '/foo', + builder: dummy, + routes: [ + GoRoute( + path: ':id', + redirect: (_, GoRouterState state) { + expect(state.pathParameters['id'], isNotNull); + return null; + }, + builder: dummy, + ), + ], + ), + ], + ); + await tester.pumpWidget( + MaterialApp.router( + key: UniqueKey(), + routerConfig: router, + ), + ); + router.push('/foo/123'); + await tester.pump(); // wait reportRouteInformation + await tester.pumpWidget( + MaterialApp.router( + key: UniqueKey(), + routerConfig: router, + ), + ); + }); } class TestInheritedNotifier extends InheritedNotifier> { diff --git a/packages/go_router/test/routing_config_test.dart b/packages/go_router/test/routing_config_test.dart index 87a866dd6464..da36885e0037 100644 --- a/packages/go_router/test/routing_config_test.dart +++ b/packages/go_router/test/routing_config_test.dart @@ -106,4 +106,48 @@ void main() { await tester.pumpAndSettle(); expect(find.text('error'), findsOneWidget); }); + + testWidgets('routing config works with named route', + (WidgetTester tester) async { + final ValueNotifier config = ValueNotifier( + RoutingConfig( + routes: [ + GoRoute(path: '/', builder: (_, __) => const Text('home')), + GoRoute( + path: '/abc', + name: 'abc', + builder: (_, __) => const Text('/abc')), + ], + ), + ); + final GoRouter router = await createRouterWithRoutingConfig( + config, + tester, + errorBuilder: (_, __) => const Text('error'), + ); + expect(find.text('home'), findsOneWidget); + // Sanity check. + router.goNamed('abc'); + await tester.pumpAndSettle(); + expect(find.text('/abc'), findsOneWidget); + + config.value = RoutingConfig( + routes: [ + GoRoute( + path: '/', name: 'home', builder: (_, __) => const Text('home')), + GoRoute( + path: '/abc', name: 'def', builder: (_, __) => const Text('def')), + ], + ); + await tester.pumpAndSettle(); + expect(find.text('def'), findsOneWidget); + + router.goNamed('home'); + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); + + router.goNamed('def'); + await tester.pumpAndSettle(); + expect(find.text('def'), findsOneWidget); + }); } diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index 76ec2874a7a2..3db0b4579086 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -4,6 +4,8 @@ // ignore_for_file: cascade_invocations, diagnostic_describe_all_properties +import 'dart:convert'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -167,6 +169,7 @@ Future createRouter( GlobalKey? navigatorKey, GoRouterWidgetBuilder? errorBuilder, String? restorationScopeId, + Codec? extraCodec, GoExceptionHandler? onException, bool requestFocus = true, bool overridePlatformDefaultLocation = false, @@ -174,6 +177,7 @@ Future createRouter( final GoRouter goRouter = GoRouter( routes: routes, redirect: redirect, + extraCodec: extraCodec, initialLocation: initialLocation, onException: onException, initialExtra: initialExtra, @@ -391,3 +395,27 @@ RouteConfiguration createRouteConfiguration({ )), navigatorKey: navigatorKey); } + +class SimpleDependencyProvider extends InheritedNotifier { + const SimpleDependencyProvider( + {super.key, required SimpleDependency dependency, required super.child}) + : super(notifier: dependency); + + static SimpleDependency of(BuildContext context) { + final SimpleDependencyProvider result = + context.dependOnInheritedWidgetOfExactType()!; + return result.notifier!; + } +} + +class SimpleDependency extends ChangeNotifier { + bool get boolProperty => _boolProperty; + bool _boolProperty = true; + set boolProperty(bool value) { + if (value == _boolProperty) { + return; + } + _boolProperty = value; + notifyListeners(); + } +} diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 2afdfb41d0f0..1a647649152f 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.4 + +* Fixes a bug of typeArguments losing NullabilitySuffix + ## 2.3.3 * Adds `initialLocation` for `StatefulShellBranchConfig` diff --git a/packages/go_router_builder/lib/src/type_helpers.dart b/packages/go_router_builder/lib/src/type_helpers.dart index bd8cc7f31d1b..b17e88b2d52e 100644 --- a/packages/go_router_builder/lib/src/type_helpers.dart +++ b/packages/go_router_builder/lib/src/type_helpers.dart @@ -94,7 +94,7 @@ String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap'; String _stateValueAccess(ParameterElement element, Set pathParameters) { if (element.isExtraField) { - return 'extra as ${element.type.getDisplayString(withNullability: element.isOptional)}'; + return 'extra as ${element.type.getDisplayString(withNullability: true)}'; } late String access; diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index cf48de720f07..b53161dfc351 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 2.3.3 +version: 2.3.4 repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22 diff --git a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart new file mode 100644 index 000000000000..2d7f8a17ac04 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.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. + +import 'package:go_router/go_router.dart'; + +@TypedGoRoute( + path: '/default-value-route') +class RequiredNullableTypeArgumentsExtraValueRoute extends GoRouteData { + RequiredNullableTypeArgumentsExtraValueRoute({required this.$extra}); + final List $extra; +} diff --git a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect new file mode 100644 index 000000000000..9877d353f4df --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect @@ -0,0 +1,30 @@ +RouteBase get $requiredNullableTypeArgumentsExtraValueRoute => + GoRouteData.$route( + path: '/default-value-route', + factory: + $RequiredNullableTypeArgumentsExtraValueRouteExtension._fromState, + ); + +extension $RequiredNullableTypeArgumentsExtraValueRouteExtension + on RequiredNullableTypeArgumentsExtraValueRoute { + static RequiredNullableTypeArgumentsExtraValueRoute _fromState( + GoRouterState state) => + RequiredNullableTypeArgumentsExtraValueRoute( + $extra: state.extra as List, + ); + + String get location => GoRouteData.$location( + '/default-value-route', + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); + + void replace(BuildContext context) => + context.replace(location, extra: $extra); +} 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 1353c5944903..63351a474950 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 @@ -366,6 +366,14 @@ void runTests() { ); final GoogleMapController controller = await controllerCompleter.future; + await tester.pumpAndSettle(); + + // TODO(mossmana): Adding this delay addresses + // https://github.com/flutter/flutter/issues/131783. It may be related + // to https://github.com/flutter/flutter/issues/54758 and should be + // re-evaluated when that issue is fixed. + await Future.delayed(const Duration(seconds: 1)); + bool iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); 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 ed0bc555140c..a31b4113d237 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.5.3 + +* Updates `com.google.android.gms:play-services-maps` to 18.2.0. + +## 2.5.2 + +* Updates annotations lib to 1.7.0. + ## 2.5.1 * Adds pub topics to package metadata. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle index f92ca4a5b87d..93ad81bf1547 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle @@ -39,8 +39,8 @@ android { } dependencies { - implementation "androidx.annotation:annotation:1.5.0" - implementation 'com.google.android.gms:play-services-maps:18.1.0' + implementation "androidx.annotation:annotation:1.7.0" + implementation 'com.google.android.gms:play-services-maps:18.2.0' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java index 271c63bdc25c..f21e6ca75ef0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import com.google.android.gms.internal.maps.zzad; +import com.google.android.gms.internal.maps.zzag; import com.google.android.gms.maps.model.Polygon; import org.junit.Test; import org.mockito.Mockito; @@ -16,7 +16,7 @@ public class PolygonControllerTest { @Test public void controller_SetsStrokeDensity() { - final zzad z = mock(zzad.class); + final zzag z = mock(zzag.class); final Polygon polygon = spy(new Polygon(z)); final float density = 5; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java index abb98627b35a..695f9a4e4498 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import com.google.android.gms.internal.maps.zzag; +import com.google.android.gms.internal.maps.zzaj; import com.google.android.gms.maps.model.Polyline; import org.junit.Test; import org.mockito.Mockito; @@ -16,7 +16,7 @@ public class PolylineControllerTest { @Test public void controller_SetsStrokeDensity() { - final zzag z = mock(zzag.class); + final zzaj z = mock(zzaj.class); final Polyline polyline = spy(new Polyline(z)); final float density = 5; 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 18af7eb63be1..cfca957b441c 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.5.1 +version: 2.5.3 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 9668392da3e0..7e5b8665a62b 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.6 + +* Updates README to direct to google_sign_in_ios README for iOS integration instructions. + ## 6.1.5 * Adds pub topics to package metadata. diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md index 154e683c3767..87f285718058 100644 --- a/packages/google_sign_in/google_sign_in/README.md +++ b/packages/google_sign_in/google_sign_in/README.md @@ -27,59 +27,7 @@ Otherwise, you may encounter `APIException` errors. ### iOS integration -1. [First register your application](https://firebase.google.com/docs/ios/setup). -2. Make sure the file you download in step 1 is named - `GoogleService-Info.plist`. -3. Move or copy `GoogleService-Info.plist` into the `[my_project]/ios/Runner` - directory. -4. Open Xcode, then right-click on `Runner` directory and select - `Add Files to "Runner"`. -5. Select `GoogleService-Info.plist` from the file manager. -6. A dialog will show up and ask you to select the targets, select the `Runner` - target. -7. If you need to authenticate to a backend server you can add a - `SERVER_CLIENT_ID` key value pair in your `GoogleService-Info.plist`. - ```xml - SERVER_CLIENT_ID - [YOUR SERVER CLIENT ID] - ``` -8. Then add the `CFBundleURLTypes` attributes below into the - `[my_project]/ios/Runner/Info.plist` file. - -```xml - - -CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - - - com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn - - - - -``` - -As an alternative to adding `GoogleService-Info.plist` to your Xcode project, -you can instead configure your app in Dart code. In this case, skip steps 3 to 7 - and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: - -```dart -GoogleSignIn _googleSignIn = GoogleSignIn( - ... - // The OAuth client id of your app. This is required. - clientId: ..., - // If you need to authenticate to a backend server, specify its OAuth client. This is optional. - serverClientId: ..., -); -``` - -Note that step 8 is still required. +Please see [instructions on integrating Google Sign-In for iOS](https://pub.dev/packages/google_sign_in_ios#ios-integration). #### iOS additional requirement @@ -212,12 +160,12 @@ For more details, take a look at the ### Does an app always need to check `canAccessScopes`? -The new web SDK implicitly grant access to the `email`, `profile` and `openid` +The new web SDK implicitly grant access to the `email`, `profile` and `openid` scopes when users complete the sign-in process (either via the One Tap UX or the Google Sign In button). If an app only needs an `idToken`, or only requests permissions to any/all of -the three scopes mentioned above +the three scopes mentioned above ([OpenID Connect scopes](https://developers.google.com/identity/protocols/oauth2/scopes#openid-connect)), it won't need to implement any additional scope handling. diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj index 45b4e802664b..5af6f7cec134 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; @@ -39,7 +38,6 @@ 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -118,7 +116,6 @@ 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); @@ -173,7 +170,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -204,7 +201,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f4569c48ce10..8e83ef7194ee 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIApplicationSupportsIndirectInputEvents + GIDClientID + 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com + GIDServerClientID + YOUR_SERVER_CLIENT_ID diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 2f3767f0cdb4..05d8bfbc6de7 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.5 +version: 6.1.6 environment: sdk: ">=2.19.0 <4.0.0" @@ -43,5 +43,6 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /example/android/app/google-services.json - - /example/ios/Runner/GoogleService-Info.plist + - /example/ios/Runner/Info.plist + - /example/ios/RunnerTests/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart index 86b83cf4a09b..94fabc58aec4 100644 --- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart @@ -81,6 +81,28 @@ void main() { verify(mockPlatform.signIn()); }); + test( + 'clientId and serverClientId parameters is forwarded to implementation', + () async { + // #docregion GoogleSignIn + final GoogleSignIn googleSignIn = GoogleSignIn( + // The OAuth client id of your app. This is required. + clientId: 'Your Client ID', + // If you need to authenticate to a backend server, specify its OAuth client. This is optional. + serverClientId: 'Your Server ID', + ); + // #enddocregion GoogleSignIn + + await googleSignIn.signIn(); + + _verifyInit( + mockPlatform, + clientId: 'Your Client ID', + serverClientId: 'Your Server ID', + ); + verify(mockPlatform.signIn()); + }); + test('forceCodeForRefreshToken sent with init method call', () async { final GoogleSignIn googleSignIn = GoogleSignIn(forceCodeForRefreshToken: true); 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 4f67a64510f3..286413207067 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.6.5 + +* Upgrades GoogleSignIn iOS SDK to 7.0. + ## 5.6.4 * Converts platform communication to Pigeon. diff --git a/packages/google_sign_in/google_sign_in_ios/README.md b/packages/google_sign_in/google_sign_in_ios/README.md index 6a5d862d77e3..29bcc25fb18f 100644 --- a/packages/google_sign_in/google_sign_in_ios/README.md +++ b/packages/google_sign_in/google_sign_in_ios/README.md @@ -13,3 +13,62 @@ should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/google_sign_in [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin + +### iOS integration + +1. [Create a Firebase project](https://firebase.google.com/docs/ios/setup#create-firebase-project) + and [register your application](https://firebase.google.com/docs/ios/setup#register-app). +2. [Enable Google Sign-In for your Firebase project](https://firebase.google.com/docs/auth/ios/google-signin#enable_google_sign-in_for_your_firebase_project). +3. Make sure to download a new copy of your project's + `GoogleService-Info.plist` from step 2. Do not put this file in your project. +4. Add the client ID from the `GoogleService-Info.plist` into your app's + `[my_project]/ios/Runner/Info.plist` file. + ```xml + GIDClientID + + + [YOUR IOS CLIENT ID] + ``` +5. If you need to authenticate to a backend server you can add a + `GIDServerClientID` key value pair in your `[my_project]/ios/Runner/Info.plist` file. + ```xml + GIDServerClientID + [YOUR SERVER CLIENT ID] + ``` +6. Then add the `CFBundleURLTypes` attributes below into the + `[my_project]/ios/Runner/Info.plist` file. + +```xml + + +CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + + com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn + + + + +``` + +As an alternative to editing the `Info.plist` in your Xcode project, +you can instead configure your app in Dart code. In this case, skip steps 4 to 5 + and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: + + +```dart +final GoogleSignIn googleSignIn = GoogleSignIn( + // The OAuth client id of your app. This is required. + clientId: 'Your Client ID', + // If you need to authenticate to a backend server, specify its OAuth client. This is optional. + serverClientId: 'Your Server ID', +); +``` + +Note that step 6 is still required. diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj index abbb5ef47ca1..ca1508eba8ca 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; + 78A36DA12AF5761E00CBFD43 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; @@ -163,7 +163,6 @@ 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); @@ -191,6 +190,7 @@ isa = PBXGroup; children = ( F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */, + 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, F76AC1A62666D0540040C8BC /* Info.plist */, ); path = RunnerTests; @@ -316,7 +316,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, @@ -328,6 +327,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 78A36DA12AF5761E00CBFD43 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist deleted file mode 100644 index 6042aab908af..000000000000 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist +++ /dev/null @@ -1,44 +0,0 @@ - - - - - AD_UNIT_ID_FOR_BANNER_TEST - ca-app-pub-3940256099942544/2934735716 - AD_UNIT_ID_FOR_INTERSTITIAL_TEST - ca-app-pub-3940256099942544/4411468910 - CLIENT_ID - 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u - ANDROID_CLIENT_ID - 479882132969-jie8r1me6dsra60pal6ejaj8dgme3tg0.apps.googleusercontent.com - API_KEY - AIzaSyBECOwLTAN6PU4Aet1b2QLGIb3kRK8Xjew - GCM_SENDER_ID - 479882132969 - PLIST_VERSION - 1 - BUNDLE_ID - io.flutter.plugins.googleSignInExample - PROJECT_ID - my-flutter-proj - STORAGE_BUCKET - my-flutter-proj.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:479882132969:ios:2643f950e0a0da08 - DATABASE_URL - https://my-flutter-proj.firebaseio.com - SERVER_CLIENT_ID - YOUR_SERVER_CLIENT_ID - - \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist index 08fef9a9fe42..5c3953aef76a 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist @@ -60,5 +60,9 @@ UIApplicationSupportsIndirectInputEvents + GIDClientID + 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com + GIDServerClientID + YOUR_SERVER_CLIENT_ID diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner/GoogleService-Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleService-Info.plist similarity index 100% rename from packages/google_sign_in/google_sign_in/example/ios/Runner/GoogleService-Info.plist rename to packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleService-Info.plist diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m index 8e27c39a841b..de504636e65d 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m @@ -18,6 +18,7 @@ @interface FLTGoogleSignInPluginTest : XCTestCase @property(strong, nonatomic) NSObject *mockPluginRegistrar; @property(strong, nonatomic) FLTGoogleSignInPlugin *plugin; @property(strong, nonatomic) id mockSignIn; +@property(strong, nonatomic) NSDictionary *googleServiceInfo; @end @@ -34,6 +35,13 @@ - (void)setUp { OCMStub(self.mockPluginRegistrar.messenger).andReturn(self.mockBinaryMessenger); self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:mockSignIn]; [FLTGoogleSignInPlugin registerWithRegistrar:self.mockPluginRegistrar]; + + NSString *plistPath = + [[NSBundle bundleForClass:[self class]] pathForResource:@"GoogleService-Info" + ofType:@"plist"]; + if (plistPath) { + self.googleServiceInfo = [[NSDictionary alloc] initWithContentsOfFile:plistPath]; + } } - (void)testSignOut { @@ -44,7 +52,7 @@ - (void)testSignOut { } - (void)testDisconnect { - [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], nil]]; + [[self.mockSignIn stub] disconnectWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin disconnectWithCompletion:^(FlutterError *error) { @@ -58,7 +66,7 @@ - (void)testDisconnectIgnoresError { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; - [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:error, nil]]; + [[self.mockSignIn stub] disconnectWithCompletion:[OCMArg invokeBlockWithArgs:error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin disconnectWithCompletion:^(FlutterError *error) { @@ -70,7 +78,7 @@ - (void)testDisconnectIgnoresError { #pragma mark - Init -- (void)testInitNoClientIdError { +- (void)testInitNoClientIdNoError { // Init plugin without GoogleService-Info.plist. self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn withGoogleServiceProperties:nil]; @@ -83,10 +91,12 @@ - (void)testInitNoClientIdError { FlutterError *error; [self.plugin initializeSignInWithParameters:params error:&error]; - XCTAssertEqualObjects(error.code, @"missing-config"); + XCTAssertNil(error); } - (void)testInitGoogleServiceInfoPlist { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; FSIInitParams *params = [FSIInitParams makeWithScopes:@[] hostedDomain:@"example.com" clientId:nil @@ -100,19 +110,17 @@ - (void)testInitGoogleServiceInfoPlist { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; OCMVerify([self.mockSignIn - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - // Set in example app GoogleService-Info.plist. - return - [configuration.hostedDomain isEqualToString:@"example.com"] && - [configuration.clientID - isEqualToString: - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"] && - [configuration.serverClientID isEqualToString:@"YOUR_SERVER_CLIENT_ID"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, @"example.com"); + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, @"YOUR_SERVER_CLIENT_ID"); } - (void)testInitDynamicClientIdNullDomain { @@ -133,17 +141,19 @@ - (void)testInitDynamicClientIdNullDomain { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; OCMVerify([self.mockSignIn - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - return configuration.hostedDomain == nil && - [configuration.clientID isEqualToString:@"mockClientId"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, nil); + XCTAssertEqualObjects(self.plugin.configuration.clientID, @"mockClientId"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, nil); } - (void)testInitDynamicServerClientIdNullDomain { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; FSIInitParams *params = [FSIInitParams makeWithScopes:@[] hostedDomain:nil clientId:nil @@ -156,14 +166,36 @@ - (void)testInitDynamicServerClientIdNullDomain { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; OCMVerify([self.mockSignIn - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - return configuration.hostedDomain == nil && - [configuration.serverClientID isEqualToString:@"mockServerClientId"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, nil); + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, @"mockServerClientId"); +} + +- (void)testInitInfoPlist { + FSIInitParams *params = [FSIInitParams makeWithScopes:@[ @"scope1" ] + hostedDomain:@"example.com" + clientId:nil + serverClientId:nil]; + + FlutterError *error; + self.plugin = [[FLTGoogleSignInPlugin alloc] init]; + [self.plugin initializeSignInWithParameters:params error:&error]; + XCTAssertNil(error); + XCTAssertNil(self.plugin.configuration); + XCTAssertNotNil(self.plugin.requestedScopes); + // Set in example app Info.plist. + XCTAssertEqualObjects( + self.plugin.signIn.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.signIn.configuration.serverClientID, @"YOUR_SERVER_CLIENT_ID"); } #pragma mark - Is signed in @@ -193,7 +225,8 @@ - (void)testSignInSilently { OCMStub([mockUser userID]).andReturn(@"mockID"); [[self.mockSignIn stub] - restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + restorePreviousSignInWithCompletion:[OCMArg + invokeBlockWithArgs:mockUser, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -215,7 +248,7 @@ - (void)testSignInSilentlyWithError { userInfo:nil]; [[self.mockSignIn stub] - restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + restorePreviousSignInWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -229,6 +262,8 @@ - (void)testSignInSilentlyWithError { #pragma mark - Sign in - (void)testSignIn { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; id mockUser = OCMClassMock([GIDGoogleUser class]); id mockUserProfile = OCMClassMock([GIDProfileData class]); OCMStub([mockUserProfile name]).andReturn(@"mockDisplay"); @@ -239,18 +274,17 @@ - (void)testSignIn { OCMStub([mockUser profile]).andReturn(mockUserProfile); OCMStub([mockUser userID]).andReturn(@"mockID"); - OCMStub([mockUser serverAuthCode]).andReturn(@"mockAuthCode"); + + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); + OCMStub([mockSignInResult serverAuthCode]).andReturn(@"mockAuthCode"); [[self.mockSignIn expect] - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - return [configuration.clientID - isEqualToString: - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:@[] - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:@[] + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -264,6 +298,11 @@ - (void)testSignIn { }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + OCMVerifyAll(self.mockSignIn); } @@ -278,16 +317,18 @@ - (void)testSignInWithInitializedScopes { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); [[self.mockSignIn expect] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { - return [[NSSet setWithArray:scopes] - isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", nil]]; - }] - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { + return [[NSSet setWithArray:scopes] + isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", nil]]; + }] + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -303,20 +344,22 @@ - (void)testSignInWithInitializedScopes { - (void)testSignInAlreadyGranted { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); [[self.mockSignIn stub] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; - [[self.mockSignIn stub] addScopes:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[self.mockSignIn currentUser] addScopes:OCMOCK_ANY + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -332,11 +375,10 @@ - (void)testSignInError { code:kGIDSignInErrorCodeCanceled userInfo:nil]; [[self.mockSignIn stub] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -348,11 +390,10 @@ - (void)testSignInError { } - (void)testSignInException { - OCMExpect([self.mockSignIn signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:OCMOCK_ANY - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]) + OCMExpect([self.mockSignIn signInWithPresentingViewController:OCMOCK_ANY + hint:OCMOCK_ANY + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); __block FlutterError *error; @@ -371,14 +412,20 @@ - (void)testSignInException { - (void)testGetTokens { id mockUser = OCMClassMock([GIDGoogleUser class]); - OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + id mockUserResponse = OCMClassMock([GIDGoogleUser class]); + + id mockIdToken = OCMClassMock([GIDToken class]); + OCMStub([mockIdToken tokenString]).andReturn(@"mockIdToken"); + OCMStub([mockUserResponse idToken]).andReturn(mockIdToken); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); - OCMStub([mockAuthentication idToken]).andReturn(@"mockIdToken"); - OCMStub([mockAuthentication accessToken]).andReturn(@"mockAccessToken"); - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:mockAuthentication, [NSNull null], nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + id mockAccessToken = OCMClassMock([GIDToken class]); + OCMStub([mockAccessToken tokenString]).andReturn(@"mockAccessToken"); + OCMStub([mockUserResponse accessToken]).andReturn(mockAccessToken); + + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:mockUserResponse, + [NSNull null], nil]]; + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -394,13 +441,11 @@ - (void)testGetTokensNoAuthKeychainError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -416,13 +461,11 @@ - (void)testGetTokensCancelledError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeCanceled userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -438,11 +481,9 @@ - (void)testGetTokensURLError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -458,11 +499,9 @@ - (void)testGetTokensUnknownError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -477,13 +516,6 @@ - (void)testGetTokensUnknownError { #pragma mark - Request scopes - (void)testRequestScopesResultErrorIfNotSignedIn { - NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeNoCurrentUser - userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] completion:^(NSNumber *success, FlutterError *error) { @@ -495,12 +527,15 @@ - (void)testRequestScopesResultErrorIfNotSignedIn { } - (void)testRequestScopesIfNoMissingScope { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] @@ -512,11 +547,35 @@ - (void)testRequestScopesIfNoMissingScope { [self waitForExpectationsWithTimeout:5.0 handler:nil]; } +- (void)testRequestScopesResultErrorIfMismatchingUser { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeMismatchWithCurrentUser + userInfo:nil]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:@[ @"mockScope1" ] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(success); + XCTAssertEqualObjects(error.code, @"mismatch_user"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + - (void)testRequestScopesWithUnknownError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] @@ -529,7 +588,10 @@ - (void)testRequestScopesWithUnknownError { } - (void)testRequestScopesException { - OCMExpect([self.mockSignIn addScopes:@[] presentingViewController:OCMOCK_ANY callback:OCMOCK_ANY]) + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + OCMExpect([mockUser addScopes:@[] presentingViewController:OCMOCK_ANY completion:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); [self.plugin requestScopes:@[] @@ -542,16 +604,18 @@ - (void)testRequestScopesException { } - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { - GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Only grant one of the two requested scopes. - OCMStub(mockUser.grantedScopes).andReturn(@[ @"mockScope1" ]); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockUser grantedScopes]).andReturn(@[ @"mockScope1" ]); + OCMStub([mockSignInResult user]).andReturn(mockUser); - [[self.mockSignIn stub] addScopes:requestedScopes - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + [[mockUser stub] addScopes:requestedScopes + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:requestedScopes @@ -564,6 +628,9 @@ - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { } - (void)testRequestsInitializedScopes { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + FSIInitParams *params = [FSIInitParams makeWithScopes:@[ @"initial1", @"initial2" ] hostedDomain:nil clientId:nil @@ -580,27 +647,28 @@ - (void)testRequestsInitializedScopes { }]; // All four scopes are requested. - [[self.mockSignIn verify] - addScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { + [[mockUser verify] addScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { return [[NSSet setWithArray:scopes] isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", @"addScope1", @"addScope2", nil]]; }] presentingViewController:OCMOCK_ANY - callback:OCMOCK_ANY]; + completion:OCMOCK_ANY]; } - (void)testRequestScopesReturnsTrueIfGranted { - GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Grant both of the requested scopes. - OCMStub(mockUser.grantedScopes).andReturn(requestedScopes); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockUser grantedScopes]).andReturn(requestedScopes); + OCMStub([mockSignInResult user]).andReturn(mockUser); - [[self.mockSignIn stub] addScopes:requestedScopes - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + [[mockUser stub] addScopes:requestedScopes + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:requestedScopes diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h index ec5403f0d3d6..503b6a4a32e5 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h @@ -7,4 +7,5 @@ #import "messages.g.h" @interface FLTGoogleSignInPlugin : NSObject + @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m index 193c15e1ee3c..5b79511921f3 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m @@ -47,20 +47,6 @@ @interface FLTGoogleSignInPlugin () -// Configuration wrapping Google Cloud Console, Google Apps, OpenID, -// and other initialization metadata. -@property(strong) GIDConfiguration *configuration; - -// Permissions requested during at sign in "init" method call -// unioned with scopes requested later with incremental authorization -// "requestScopes" method call. -// The "email" and "profile" base scopes are always implicitly requested. -@property(copy) NSSet *requestedScopes; - -// Instance used to manage Google Sign In authentication including -// sign in, sign out, and requesting additional scopes. -@property(strong, readonly) GIDSignIn *signIn; - // The contents of GoogleService-Info.plist, if it exists. @property(strong, nullable) NSDictionary *googleServiceProperties; @@ -113,21 +99,17 @@ - (void)initializeSignInWithParameters:(nonnull FSIInitParams *)params GIDConfiguration *configuration = [self configurationWithClientIdArgument:params.clientId serverClientIdArgument:params.serverClientId hostedDomainArgument:params.hostedDomain]; + self.requestedScopes = [NSSet setWithArray:params.scopes]; if (configuration != nil) { - self.requestedScopes = [NSSet setWithArray:params.scopes]; self.configuration = configuration; - } else { - *error = [FlutterError errorWithCode:@"missing-config" - message:@"GoogleService-Info.plist file not found and clientId " - @"was not provided programmatically." - details:nil]; } } - (void)signInSilentlyWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion { - [self.signIn restorePreviousSignInWithCallback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user withCompletion:completion error:error]; + [self.signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *_Nullable user, + NSError *_Nullable error) { + [self didSignInForUser:user withServerAuthCode:nil completion:completion error:error]; }]; } @@ -139,19 +121,36 @@ - (nullable NSNumber *)isSignedInWithError: - (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion { @try { - GIDConfiguration *configuration = self.configuration - ?: [self configurationWithClientIdArgument:nil - serverClientIdArgument:nil - hostedDomainArgument:nil]; - [self.signIn signInWithConfiguration:configuration - presentingViewController:[self topViewController] - hint:nil - additionalScopes:self.requestedScopes.allObjects - callback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user - withCompletion:completion - error:error]; - }]; + // If the configuration settings are passed from the Dart API, use those. + // Otherwise, use settings from the GoogleService-Info.plist if available. + // If neither are available, do not set the configuration - GIDSignIn will automatically use + // settings from the Info.plist (which is the recommended method). + if (!self.configuration && self.googleServiceProperties) { + self.configuration = [self configurationWithClientIdArgument:nil + serverClientIdArgument:nil + hostedDomainArgument:nil]; + } + if (self.configuration) { + self.signIn.configuration = self.configuration; + } + + [self.signIn signInWithPresentingViewController:[self topViewController] + hint:nil + additionalScopes:self.requestedScopes.allObjects + completion:^(GIDSignInResult *_Nullable signInResult, + NSError *_Nullable error) { + GIDGoogleUser *user; + NSString *serverAuthCode; + if (signInResult) { + user = signInResult.user; + serverAuthCode = signInResult.serverAuthCode; + } + + [self didSignInForUser:user + withServerAuthCode:serverAuthCode + completion:completion + error:error]; + }]; } @catch (NSException *e) { completion(nil, [FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); [e raise]; @@ -161,23 +160,24 @@ - (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, - (void)getAccessTokenWithCompletion:(nonnull void (^)(FSITokenData *_Nullable, FlutterError *_Nullable))completion { GIDGoogleUser *currentUser = self.signIn.currentUser; - GIDAuthentication *auth = currentUser.authentication; - [auth doWithFreshTokens:^void(GIDAuthentication *authentication, NSError *error) { + [currentUser refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *_Nullable user, + NSError *_Nullable error) { if (error) { completion(nil, getFlutterError(error)); } else { - completion([FSITokenData makeWithIdToken:authentication.idToken - accessToken:authentication.accessToken], + completion([FSITokenData makeWithIdToken:user.idToken.tokenString + accessToken:user.accessToken.tokenString], nil); } }]; } -- (void)signOutWithError:(FlutterError *_Nullable *_Nonnull)error; -{ [self.signIn signOut]; } +- (void)signOutWithError:(FlutterError *_Nullable *_Nonnull)error { + [self.signIn signOut]; +} - (void)disconnectWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - [self.signIn disconnectWithCallback:^(NSError *error) { + [self.signIn disconnectWithCompletion:^(NSError *_Nullable error) { // TODO(stuartmorgan): This preserves the pre-Pigeon-migration behavior, but it's unclear why // 'error' is being ignored here. completion(nil); @@ -190,31 +190,38 @@ - (void)requestScopes:(nonnull NSArray *)scopes NSSet *requestedScopes = self.requestedScopes; @try { - [self.signIn addScopes:requestedScopes.allObjects + GIDGoogleUser *currentUser = self.signIn.currentUser; + if (currentUser == nil) { + completion(nil, [FlutterError errorWithCode:@"sign_in_required" + message:@"No account to grant scopes." + details:nil]); + } + [currentUser addScopes:requestedScopes.allObjects presentingViewController:[self topViewController] - callback:^(GIDGoogleUser *addedScopeUser, NSError *addedScopeError) { - BOOL granted = NO; - FlutterError *error = nil; - if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == kGIDSignInErrorCodeNoCurrentUser) { - error = [FlutterError errorWithCode:@"sign_in_required" - message:@"No account to grant scopes." - details:nil]; - } else if ([addedScopeError.domain - isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == - kGIDSignInErrorCodeScopesAlreadyGranted) { - // Scopes already granted, report success. - granted = YES; - } else if (addedScopeUser == nil) { - granted = NO; - } else { - NSSet *grantedScopes = - [NSSet setWithArray:addedScopeUser.grantedScopes]; - granted = [requestedScopes isSubsetOfSet:grantedScopes]; - } - completion(error == nil ? @(granted) : nil, error); - }]; + completion:^(GIDSignInResult *_Nullable signInResult, + NSError *_Nullable addedScopeError) { + BOOL granted = NO; + FlutterError *error = nil; + + if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == kGIDSignInErrorCodeMismatchWithCurrentUser) { + error = + [FlutterError errorWithCode:@"mismatch_user" + message:@"There is an operation on a previous " + @"user. Try signing in again." + details:nil]; + } else if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == + kGIDSignInErrorCodeScopesAlreadyGranted) { + // Scopes already granted, report success. + granted = YES; + } else if (signInResult.user) { + NSSet *grantedScopes = + [NSSet setWithArray:signInResult.user.grantedScopes]; + granted = [requestedScopes isSubsetOfSet:grantedScopes]; + } + completion(error == nil ? @(granted) : nil, error); + }]; } @catch (NSException *e) { completion(nil, [FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]); } @@ -265,7 +272,8 @@ - (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg } - (void)didSignInForUser:(GIDGoogleUser *)user - withCompletion:(nonnull void (^)(FSIUserData *_Nullable, + withServerAuthCode:(NSString *_Nullable)serverAuthCode + completion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion error:(NSError *)error { if (error != nil) { @@ -277,11 +285,16 @@ - (void)didSignInForUser:(GIDGoogleUser *)user // Placeholder that will be replaced by on the Dart side based on screen size. photoUrl = [user.profile imageURLWithDimension:1337]; } + NSString *idToken; + if (user.idToken) { + idToken = user.idToken.tokenString; + } completion([FSIUserData makeWithDisplayName:user.profile.name email:user.profile.email userId:user.userID photoUrl:[photoUrl absoluteString] - serverAuthCode:user.serverAuthCode], + serverAuthCode:serverAuthCode + idToken:idToken], nil); } } diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h index 17ddb7f616bc..fc18c9b9e513 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h @@ -6,6 +6,8 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN @class GIDSignIn; @@ -13,6 +15,20 @@ NS_ASSUME_NONNULL_BEGIN /// Methods exposed for unit testing. @interface FLTGoogleSignInPlugin () +// Configuration wrapping Google Cloud Console, Google Apps, OpenID, +// and other initialization metadata. +@property(strong) GIDConfiguration *configuration; + +// Permissions requested during at sign in "init" method call +// unioned with scopes requested later with incremental authorization +// "requestScopes" method call. +// The "email" and "profile" base scopes are always implicitly requested. +@property(copy) NSSet *requestedScopes; + +// Instance used to manage Google Sign In authentication including +// sign in, sign out, and requesting additional scopes. +@property(strong, readonly) GIDSignIn *signIn; + /// Inject @c GIDSignIn for testing. - (instancetype)initWithSignIn:(GIDSignIn *)signIn; diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h index 37493aa26c6a..745c1ec91802 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h @@ -43,12 +43,14 @@ NS_ASSUME_NONNULL_BEGIN email:(NSString *)email userId:(NSString *)userId photoUrl:(nullable NSString *)photoUrl - serverAuthCode:(nullable NSString *)serverAuthCode; + serverAuthCode:(nullable NSString *)serverAuthCode + idToken:(nullable NSString *)idToken; @property(nonatomic, copy, nullable) NSString *displayName; @property(nonatomic, copy) NSString *email; @property(nonatomic, copy) NSString *userId; @property(nonatomic, copy, nullable) NSString *photoUrl; @property(nonatomic, copy, nullable) NSString *serverAuthCode; +@property(nonatomic, copy, nullable) NSString *idToken; @end /// Pigeon version of GoogleSignInTokenData. diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m index 4d617ca81f08..96d6b54232a5 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m @@ -86,13 +86,15 @@ + (instancetype)makeWithDisplayName:(nullable NSString *)displayName email:(NSString *)email userId:(NSString *)userId photoUrl:(nullable NSString *)photoUrl - serverAuthCode:(nullable NSString *)serverAuthCode { + serverAuthCode:(nullable NSString *)serverAuthCode + idToken:(nullable NSString *)idToken { FSIUserData *pigeonResult = [[FSIUserData alloc] init]; pigeonResult.displayName = displayName; pigeonResult.email = email; pigeonResult.userId = userId; pigeonResult.photoUrl = photoUrl; pigeonResult.serverAuthCode = serverAuthCode; + pigeonResult.idToken = idToken; return pigeonResult; } + (FSIUserData *)fromList:(NSArray *)list { @@ -104,6 +106,7 @@ + (FSIUserData *)fromList:(NSArray *)list { NSAssert(pigeonResult.userId != nil, @""); pigeonResult.photoUrl = GetNullableObjectAtIndex(list, 3); pigeonResult.serverAuthCode = GetNullableObjectAtIndex(list, 4); + pigeonResult.idToken = GetNullableObjectAtIndex(list, 5); return pigeonResult; } + (nullable FSIUserData *)nullableFromList:(NSArray *)list { @@ -116,6 +119,7 @@ - (NSArray *)toList { (self.userId ?: [NSNull null]), (self.photoUrl ?: [NSNull null]), (self.serverAuthCode ?: [NSNull null]), + (self.idToken ?: [NSNull null]), ]; } @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec b/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec index 7739005bdd40..9658184c8839 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec +++ b/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec @@ -16,7 +16,7 @@ Enables Google Sign-In in Flutter apps. s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/FLTGoogleSignInPlugin.modulemap' s.dependency 'Flutter' - s.dependency 'GoogleSignIn', '~> 6.2' + s.dependency 'GoogleSignIn', '~> 7.0' s.static_framework = true s.platform = :ios, '11.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart index 73113abb49a7..9c589501114a 100644 --- a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart +++ b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart @@ -103,6 +103,7 @@ class GoogleSignInIOS extends GoogleSignInPlatform { displayName: data.displayName, photoUrl: data.photoUrl, serverAuthCode: data.serverAuthCode, + idToken: data.idToken, ); } diff --git a/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart index 78fa37259bea..21dd2ebf8e2a 100644 --- a/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart +++ b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart @@ -60,6 +60,7 @@ class UserData { required this.userId, this.photoUrl, this.serverAuthCode, + this.idToken, }); String? displayName; @@ -72,6 +73,8 @@ class UserData { String? serverAuthCode; + String? idToken; + Object encode() { return [ displayName, @@ -79,6 +82,7 @@ class UserData { userId, photoUrl, serverAuthCode, + idToken, ]; } @@ -90,6 +94,7 @@ class UserData { userId: result[2]! as String, photoUrl: result[3] as String?, serverAuthCode: result[4] as String?, + idToken: result[5] as String?, ); } } diff --git a/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart index f92f1d06c631..2baf9af82ad8 100644 --- a/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart +++ b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart @@ -43,6 +43,7 @@ class UserData { this.displayName, this.photoUrl, this.serverAuthCode, + this.idToken, }); final String? displayName; @@ -50,6 +51,7 @@ class UserData { final String userId; final String? photoUrl; final String? serverAuthCode; + final String? idToken; } /// Pigeon version of GoogleSignInTokenData. 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 c2e780e4ac40..d6a9e975fa9e 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.6.4 +version: 5.6.5 environment: sdk: ">=2.19.0 <4.0.0" @@ -36,6 +36,7 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - - /example/ios/Runner/GoogleService-Info.plist + - /example/ios/Runner/Info.plist + - /example/ios/RunnerTests/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m - /example/lib/main.dart diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart index ace1a0fec303..d76bc978fc72 100644 --- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart @@ -13,12 +13,12 @@ import 'package:mockito/mockito.dart'; import 'google_sign_in_ios_test.mocks.dart'; final GoogleSignInUserData _user = GoogleSignInUserData( - email: 'john.doe@gmail.com', - id: '8162538176523816253123', - photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', - displayName: 'John Doe', - serverAuthCode: '789', -); + email: 'john.doe@gmail.com', + id: '8162538176523816253123', + photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', + displayName: 'John Doe', + serverAuthCode: '789', + idToken: '123'); final GoogleSignInTokenData _token = GoogleSignInTokenData( idToken: '123', @@ -60,6 +60,7 @@ void main() { photoUrl: _user.photoUrl, displayName: _user.displayName, serverAuthCode: _user.serverAuthCode, + idToken: _user.idToken, )); final dynamic response = await googleSignIn.signInSilently(); @@ -82,6 +83,7 @@ void main() { photoUrl: _user.photoUrl, displayName: _user.displayName, serverAuthCode: _user.serverAuthCode, + idToken: _user.idToken, )); final dynamic response = await googleSignIn.signIn(); 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 0a6f7259b187..90aeabceebea 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.1 + +* Enables FedCM on browsers that support this authentication mechanism. +* Uses the expiration timestamps of Credential and Token responses to improve + the accuracy of `isSignedIn` and `canAccessScopes` methods. +* Deprecates `signIn()` method. + * Users should migrate to `renderButton` and `silentSignIn`, as described in + the README. + ## 0.12.0+5 * Migrates to `dart:ui_web` APIs. diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart index 336b626c11de..07515ec1a920 100644 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart @@ -24,6 +24,11 @@ final CredentialResponse minimalCredential = 'credential': minimalJwtToken, }); +final CredentialResponse expiredCredential = + jsifyAs({ + 'credential': expiredJwtToken, +}); + /// A JWT token with predefined values. /// /// 'email': 'adultman@example.com', @@ -55,11 +60,30 @@ const String minimalJwtToken = /// The payload of a JWT token that contains only non-nullable values. /// -/// "email": "adultman@example.com", -/// "sub": "123456" +/// 'email': 'adultman@example.com', +/// 'sub': '123456' const String minimalPayload = 'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2In0'; +/// A JWT token with minimal set of predefined values and an expiration timestamp. +/// +/// 'email': 'adultman@example.com', +/// 'sub': '123456', +/// 'exp': 1430330400 +/// +/// Signed with HS256 and the private key: 'symmetric-encryption-is-weak' +const String expiredJwtToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.$expiredPayload.--gb5tnVSSsLg4zjjVH0FUUvT4rbehIcnBhB-8Iekm4'; + +/// The payload of a JWT token that contains only non-nullable values, and an +/// expiration timestamp of 1430330400 (Wednesday, April 29, 2015 6:00:00 PM UTC) +/// +/// 'email': 'adultman@example.com', +/// 'sub': '123456', +/// 'exp': 1430330400 +const String expiredPayload = + 'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwiZXhwIjoxNDMwMzMwNDAwfQ'; + // More encrypted JWT Tokens may be created on https://jwt.io. // // First, decode the `goodJwtToken` above, modify to your heart's diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart index 6f5fcfd97efa..9dec77b81bde 100644 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart @@ -85,6 +85,30 @@ void main() { }); }); + group('getCredentialResponseExpirationTimestamp', () { + testWidgets('Good payload -> data', (_) async { + final DateTime? expiration = + getCredentialResponseExpirationTimestamp(expiredCredential); + + expect(expiration, isNotNull); + expect(expiration!.millisecondsSinceEpoch, 1430330400 * 1000); + }); + + testWidgets('No expiration -> null', (_) async { + expect( + getCredentialResponseExpirationTimestamp(minimalCredential), isNull); + }); + + testWidgets('Bad data -> null', (_) async { + final CredentialResponse bogus = + jsifyAs({ + 'credential': 'some-bogus.thing-that-is-not.valid-jwt', + }); + + expect(getCredentialResponseExpirationTimestamp(bogus), isNull); + }); + }); + group('getJwtTokenPayload', () { testWidgets('happy case -> data', (_) async { final Map? data = getJwtTokenPayload(goodJwtToken); diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart b/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart index c8d6ebfe1dca..cf79de8a3a20 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart @@ -41,6 +41,8 @@ class GisSdkClient { _initializeIdClient( clientId, onResponse: _onCredentialResponse, + hostedDomain: hostedDomain, + useFedCM: true, ); _tokenClient = _initializeTokenClient( @@ -64,6 +66,8 @@ class GisSdkClient { _tokenResponses.stream.listen((TokenResponse response) { _lastTokenResponse = response; + _lastTokenResponseExpiration = + DateTime.now().add(Duration(seconds: response.expires_in)); }, onError: (Object error) { _logIfEnabled('Error on TokenResponse:', [error.toString()]); _lastTokenResponse = null; @@ -102,6 +106,8 @@ class GisSdkClient { void _initializeIdClient( String clientId, { required CallbackFn onResponse, + String? hostedDomain, + bool? useFedCM, }) { // Initialize `id` for the silent-sign in code. final IdConfiguration idConfig = IdConfiguration( @@ -109,6 +115,9 @@ class GisSdkClient { callback: allowInterop(onResponse), cancel_on_tap_outside: false, auto_select: true, // Attempt to sign-in silently. + hd: hostedDomain, + use_fedcm_for_prompt: + useFedCM, // Use the native browser prompt, when available. ); id.initialize(idConfig); } @@ -230,6 +239,8 @@ class GisSdkClient { return id.renderButton(parent, convertButtonConfiguration(options)!); } + // TODO(dit): Clean this up. https://github.com/flutter/flutter/issues/137727 + // /// Starts an oauth2 "implicit" flow to authorize requests. /// /// The new GIS SDK does not return user authentication from this flow, so: @@ -238,7 +249,15 @@ class GisSdkClient { /// * If [_lastCredentialResponse] is null, we add [people.scopes] to the /// [_initialScopes], so we can retrieve User Profile information back /// from the People API (without idToken). See [people.requestUserData]. + @Deprecated( + 'Use `renderButton` instead. See: https://pub.dev/packages/google_sign_in_web#migrating-to-v011-and-v012-google-identity-services') Future signIn() async { + // Warn users that this method will be removed. + domConsole.warn( + 'The google_sign_in plugin `signIn` method is deprecated on the web, and will be removed in Q2 2024. Please use `renderButton` instead. See: ', + [ + 'https://pub.dev/packages/google_sign_in_web#migrating-to-v011-and-v012-google-identity-services' + ]); // If we already know the user, use their `email` as a `hint`, so they don't // have to pick their user again in the Authorization popup. final GoogleSignInUserData? knownUser = @@ -265,6 +284,8 @@ class GisSdkClient { // This function returns the currently signed-in [GoogleSignInUserData]. // // It'll do a request to the People API (if needed). + // + // TODO(dit): Clean this up. https://github.com/flutter/flutter/issues/137727 Future _computeUserDataForLastToken() async { // If the user hasn't authenticated, request their basic profile info // from the People API. @@ -302,9 +323,27 @@ class GisSdkClient { await signOut(); } - /// Returns true if the client has recognized this user before. + /// Returns true if the client has recognized this user before, and the last-seen + /// credential is not expired. Future isSignedIn() async { - return _lastCredentialResponse != null || _requestedUserData != null; + bool isSignedIn = false; + if (_lastCredentialResponse != null) { + final DateTime? expiration = utils + .getCredentialResponseExpirationTimestamp(_lastCredentialResponse); + // All Google ID Tokens provide an "exp" date. If the method above cannot + // extract `expiration`, it's because `_lastCredentialResponse`'s contents + // are unexpected (or wrong) in any way. + // + // Users are considered to be signedIn when the last CredentialResponse + // exists and has an expiration date in the future. + // + // Users are not signed in in any other case. + // + // See: https://developers.google.com/identity/openid-connect/openid-connect#an-id-tokens-payload + isSignedIn = expiration?.isAfter(DateTime.now()) ?? false; + } + + return isSignedIn || _requestedUserData != null; } /// Clears all the cached results from authentication and authorization. @@ -338,12 +377,15 @@ class GisSdkClient { /// Checks if the passed-in `accessToken` can access all `scopes`. /// /// This validates that the `accessToken` is the same as the last seen - /// token response, and uses that response to check if permissions are - /// still granted. + /// token response, that the token is not expired, then uses that response to + /// check if permissions are still granted. Future canAccessScopes(List scopes, String? accessToken) async { if (accessToken != null && _lastTokenResponse != null) { if (accessToken == _lastTokenResponse!.access_token) { - return oauth2.hasGrantedAllScopes(_lastTokenResponse!, scopes); + final bool isTokenValid = + _lastTokenResponseExpiration?.isAfter(DateTime.now()) ?? false; + return isTokenValid && + oauth2.hasGrantedAllScopes(_lastTokenResponse!, scopes); } } return false; @@ -368,6 +410,8 @@ class GisSdkClient { // The last-seen credential and token responses CredentialResponse? _lastCredentialResponse; TokenResponse? _lastTokenResponse; + // Expiration timestamp for the lastTokenResponse, which only has an `expires_in` field. + DateTime? _lastTokenResponseExpiration; /// The StreamController onto which the GIS Client propagates user authentication events. /// @@ -379,5 +423,7 @@ class GisSdkClient { // (if needed) // // (This is a synthetic _lastCredentialResponse) + // + // TODO(dit): Clean this up. https://github.com/flutter/flutter/issues/137727 GoogleSignInUserData? _requestedUserData; } diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart index 8ec9cb572017..609a387d1178 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart @@ -52,32 +52,51 @@ Map? decodeJwtPayload(String? payload) { return null; } +/// Returns the payload of a [CredentialResponse]. +Map? getResponsePayload(CredentialResponse? response) { + if (response?.credential == null) { + return null; + } + + return getJwtTokenPayload(response!.credential); +} + /// Converts a [CredentialResponse] into a [GoogleSignInUserData]. /// /// May return `null`, if the `credentialResponse` is null, or its `credential` /// cannot be decoded. GoogleSignInUserData? gisResponsesToUserData( CredentialResponse? credentialResponse) { - if (credentialResponse == null || credentialResponse.credential == null) { - return null; - } - - final Map? payload = - getJwtTokenPayload(credentialResponse.credential); - + final Map? payload = getResponsePayload(credentialResponse); if (payload == null) { return null; } + assert(credentialResponse?.credential != null, + 'The CredentialResponse cannot be null and have a payload.'); + return GoogleSignInUserData( email: payload['email']! as String, id: payload['sub']! as String, displayName: payload['name'] as String?, photoUrl: payload['picture'] as String?, - idToken: credentialResponse.credential, + idToken: credentialResponse!.credential, ); } +/// Returns the expiration timestamp ('exp') of a [CredentialResponse]. +/// +/// May return `null` if the `credentialResponse` is null, its `credential` +/// cannot be decoded, or the `exp` field is not set on the JWT payload. +DateTime? getCredentialResponseExpirationTimestamp( + CredentialResponse? credentialResponse) { + final Map? payload = getResponsePayload(credentialResponse); + // Get the 'exp' field from the payload, if present. + final int? exp = (payload != null) ? payload['exp'] as int? : null; + // Return 'exp' (a timestamp in seconds since Epoch) as a DateTime. + return (exp != null) ? DateTime.fromMillisecondsSinceEpoch(exp * 1000) : null; +} + /// Converts responses from the GIS library into TokenData for the plugin. GoogleSignInTokenData gisResponsesToTokenData( CredentialResponse? credentialResponse, TokenResponse? tokenResponse) { 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 c4a32959e0bf..e9836a35cef5 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,7 +3,7 @@ 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.0+5 +version: 0.12.1 environment: sdk: ">=3.1.0 <4.0.0" @@ -22,7 +22,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - google_identity_services_web: ^0.2.1 + google_identity_services_web: ^0.2.2 google_sign_in_platform_interface: ^2.4.0 http: ">=0.13.0 <2.0.0" js: ^0.6.3 diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 2d096e2f07c4..9b50a7e9f70b 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.8+2 + +* Updates annotations lib to 1.7.0. + ## 0.8.8+1 * Fixes NullPointerException on pre-Android 13 devices when using Android Photo Picker to pick image or video. diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index 29ee7b4e9dca..9aa57dc326d4 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -39,7 +39,7 @@ android { } dependencies { implementation 'androidx.core:core:1.10.1' - implementation 'androidx.annotation:annotation:1.3.0' + implementation 'androidx.annotation:annotation:1.7.0' implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation 'androidx.activity:activity:1.7.2' // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 8232869c93ba..a9dab5bdca55 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ 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.8+1 +version: 0.8.8+2 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 158a8811347c..4433ea9c9197 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.8.8+4 + +* Updates to Pigeon 13. + +## 0.8.8+3 + +* Fixes a possible crash when calling a picker method while another is waiting on permissions. + ## 0.8.8+2 * Adds pub topics to package metadata. diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m index 3ccca17f2e15..36ca8ec9658e 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -56,7 +56,7 @@ - (void)testPluginPickImageDeviceBack { camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@YES + fullMetadata:YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -89,7 +89,7 @@ - (void)testPluginPickImageDeviceFront { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@YES + fullMetadata:YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -174,7 +174,7 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] quality:@(50) - fullMetadata:@YES + fullMetadata:YES completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; @@ -197,8 +197,8 @@ - (void)testPickMediaShouldUseUIImagePickerControllerOnPreiOS14 { FLTMediaSelectionOptions *mediaSelectionOptions = [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] imageQuality:@(50) - requestFullMetadata:@YES - allowMultiple:@YES]; + requestFullMetadata:YES + allowMultiple:YES]; [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions completion:^(NSArray *_Nullable result, @@ -219,7 +219,7 @@ - (void)testPickImageWithoutFullMetadata { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@NO + fullMetadata:NO completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -235,7 +235,7 @@ - (void)testPickMultiImageWithoutFullMetadata { [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@NO + fullMetadata:NO completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; @@ -253,8 +253,8 @@ - (void)testPickMediaWithoutFullMetadata { FLTMediaSelectionOptions *mediaSelectionOptions = [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] imageQuality:@(50) - requestFullMetadata:@YES - allowMultiple:@YES]; + requestFullMetadata:YES + allowMultiple:YES]; [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions @@ -279,7 +279,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@YES + fullMetadata:YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -502,7 +502,7 @@ - (void)testPickImageRequestAuthorization API_AVAILABLE(ios(14)) { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@YES + fullMetadata:YES completion:^(NSString *result, FlutterError *error){ }]; OCMVerifyAll(mockPhotoLibrary); @@ -521,7 +521,7 @@ - (void)testPickImageAuthorizationDenied API_AVAILABLE(ios(14)) { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@YES + fullMetadata:YES completion:^(NSString *result, FlutterError *error) { XCTAssertNil(result); XCTAssertEqualObjects(error.code, @"photo_access_denied"); @@ -531,4 +531,81 @@ - (void)testPickImageAuthorizationDenied API_AVAILABLE(ios(14)) { [self waitForExpectationsWithTimeout:30 handler:nil]; } +- (void)testPickMultiImageDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100] + quality:nil + fullMetadata:YES + completion:^(NSArray *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100] + quality:nil + fullMetadata:YES + completion:^(NSArray *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testPickMediaDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + FLTMediaSelectionOptions *options = + [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] + imageQuality:@(50) + requestFullMetadata:YES + allowMultiple:YES]; + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickMediaWithMediaSelectionOptions:options + completion:^(NSArray *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickMediaWithMediaSelectionOptions:options + completion:^(NSArray *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testPickVideoDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([AVCaptureDevice class]); + OCMStub([mockPhotoLibrary authorizationStatusForMediaType:AVMediaTypeVideo]) + .andReturn(AVAuthorizationStatusNotDetermined); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + FLTSourceSpecification *source = [FLTSourceSpecification makeWithType:FLTSourceTypeCamera + camera:FLTSourceCameraRear]; + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickVideoWithSource:source + maxDuration:nil + completion:^(NSString *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickVideoWithSource:source + maxDuration:nil + completion:^(NSString *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + @end 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 65120c4254d7..f699ca98c2dd 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,6 @@ - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result { @interface FLTImagePickerPlugin () -/** - * The PHPickerViewController instance used to pick multiple - * images. - */ -@property(strong, nonatomic) PHPickerViewController *pickerViewController API_AVAILABLE(ios(14)); - /** * The UIImagePickerController instances that will be used when a new * controller would normally be created. Each call to @@ -54,7 +48,7 @@ @implementation FLTImagePickerPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FLTImagePickerPlugin *instance = [[FLTImagePickerPlugin alloc] init]; - FLTImagePickerApiSetup(registrar.messenger, instance); + SetUpFLTImagePickerApi(registrar.messenger, instance); } - (UIImagePickerController *)createImagePickerController { @@ -117,15 +111,16 @@ - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)con config.filter = [PHPickerFilter imagesFilter]; } - _pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; - _pickerViewController.delegate = self; - _pickerViewController.presentationController.delegate = self; + PHPickerViewController *pickerViewController = + [[PHPickerViewController alloc] initWithConfiguration:config]; + pickerViewController.delegate = self; + pickerViewController.presentationController.delegate = self; self.callContext = context; if (context.requestFullMetadata) { - [self checkPhotoAuthorizationForAccessLevel]; + [self checkPhotoAuthorizationWithPHPicker:pickerViewController]; } else { - [self showPhotoLibraryWithPHPicker:_pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } } @@ -167,7 +162,7 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source maxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality - fullMetadata:(NSNumber *)fullMetadata + fullMetadata:(BOOL)fullMetadata completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { [self cancelInProgressCall]; @@ -183,7 +178,7 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source context.maxSize = maxSize; context.imageQuality = imageQuality; context.maxImageCount = 1; - context.requestFullMetadata = [fullMetadata boolValue]; + context.requestFullMetadata = fullMetadata; if (source.type == FLTSourceTypeGallery) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { @@ -198,14 +193,15 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality - fullMetadata:(NSNumber *)fullMetadata + fullMetadata:(BOOL)fullMetadata completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:completion]; context.maxSize = maxSize; context.imageQuality = imageQuality; - context.requestFullMetadata = [fullMetadata boolValue]; + context.requestFullMetadata = fullMetadata; if (@available(iOS 14, *)) { [self launchPHPickerWithContext:context]; @@ -220,13 +216,14 @@ - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize - (void)pickMediaWithMediaSelectionOptions:(nonnull FLTMediaSelectionOptions *)mediaSelectionOptions completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:completion]; context.maxSize = [mediaSelectionOptions maxSize]; context.imageQuality = [mediaSelectionOptions imageQuality]; context.requestFullMetadata = [mediaSelectionOptions requestFullMetadata]; context.includeVideo = YES; - if (![[mediaSelectionOptions allowMultiple] boolValue]) { + if (!mediaSelectionOptions.allowMultiple) { context.maxImageCount = 1; } @@ -244,6 +241,7 @@ - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source maxDuration:(nullable NSNumber *)maxDurationSeconds completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:^void(NSArray *paths, FlutterError *error) { if (paths.count > 1) { @@ -393,7 +391,8 @@ - (void)checkPhotoAuthorizationWithImagePicker:(UIImagePickerController *)imageP } } -- (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { +- (void)checkPhotoAuthorizationWithPHPicker:(PHPickerViewController *)pickerViewController + API_AVAILABLE(ios(14)) { PHAccessLevel requestedAccessLevel = PHAccessLevelReadWrite; PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:requestedAccessLevel]; @@ -404,13 +403,9 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { - [self - showPhotoLibraryWithPHPicker:self-> - _pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } else if (status == PHAuthorizationStatusLimited) { - [self - showPhotoLibraryWithPHPicker:self-> - _pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } else { [self errorNoPhotoAccess:status]; } @@ -420,7 +415,7 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { } case PHAuthorizationStatusAuthorized: case PHAuthorizationStatusLimited: - [self showPhotoLibraryWithPHPicker:_pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h index 4e2c4b28c1f6..593f88296b8b 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -18,11 +18,23 @@ typedef NS_ENUM(NSUInteger, FLTSourceCamera) { FLTSourceCameraFront = 1, }; +/// Wrapper for FLTSourceCamera to allow for nullability. +@interface FLTSourceCameraBox : NSObject +@property(nonatomic, assign) FLTSourceCamera value; +- (instancetype)initWithValue:(FLTSourceCamera)value; +@end + typedef NS_ENUM(NSUInteger, FLTSourceType) { FLTSourceTypeCamera = 0, FLTSourceTypeGallery = 1, }; +/// Wrapper for FLTSourceType to allow for nullability. +@interface FLTSourceTypeBox : NSObject +@property(nonatomic, assign) FLTSourceType value; +- (instancetype)initWithValue:(FLTSourceType)value; +@end + @class FLTMaxSize; @class FLTMediaSelectionOptions; @class FLTSourceSpecification; @@ -38,12 +50,12 @@ typedef NS_ENUM(NSUInteger, FLTSourceType) { - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize imageQuality:(nullable NSNumber *)imageQuality - requestFullMetadata:(NSNumber *)requestFullMetadata - allowMultiple:(NSNumber *)allowMultiple; + requestFullMetadata:(BOOL)requestFullMetadata + allowMultiple:(BOOL)allowMultiple; @property(nonatomic, strong) FLTMaxSize *maxSize; @property(nonatomic, strong, nullable) NSNumber *imageQuality; -@property(nonatomic, strong) NSNumber *requestFullMetadata; -@property(nonatomic, strong) NSNumber *allowMultiple; +@property(nonatomic, assign) BOOL requestFullMetadata; +@property(nonatomic, assign) BOOL allowMultiple; @end @interface FLTSourceSpecification : NSObject @@ -61,11 +73,11 @@ NSObject *FLTImagePickerApiGetCodec(void); - (void)pickImageWithSource:(FLTSourceSpecification *)source maxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality - fullMetadata:(NSNumber *)requestFullMetadata + fullMetadata:(BOOL)requestFullMetadata completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; - (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality - fullMetadata:(NSNumber *)requestFullMetadata + fullMetadata:(BOOL)requestFullMetadata completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)pickVideoWithSource:(FLTSourceSpecification *)source @@ -77,7 +89,7 @@ NSObject *FLTImagePickerApiGetCodec(void); FlutterError *_Nullable))completion; @end -extern void FLTImagePickerApiSetup(id binaryMessenger, +extern void SetUpFLTImagePickerApi(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m index 2a24f8367037..1659cb887039 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m @@ -1,16 +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. -// Autogenerated from Pigeon (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else #import +#endif #if !__has_feature(objc_arc) #error File requires ARC to be enabled. #endif +@implementation FLTSourceCameraBox +- (instancetype)initWithValue:(FLTSourceCamera)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation FLTSourceTypeBox +- (instancetype)initWithValue:(FLTSourceType)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ @@ -60,8 +85,8 @@ + (nullable FLTMaxSize *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.width ?: [NSNull null]), - (self.height ?: [NSNull null]), + self.width ?: [NSNull null], + self.height ?: [NSNull null], ]; } @end @@ -69,8 +94,8 @@ - (NSArray *)toList { @implementation FLTMediaSelectionOptions + (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize imageQuality:(nullable NSNumber *)imageQuality - requestFullMetadata:(NSNumber *)requestFullMetadata - allowMultiple:(NSNumber *)allowMultiple { + requestFullMetadata:(BOOL)requestFullMetadata + allowMultiple:(BOOL)allowMultiple { FLTMediaSelectionOptions *pigeonResult = [[FLTMediaSelectionOptions alloc] init]; pigeonResult.maxSize = maxSize; pigeonResult.imageQuality = imageQuality; @@ -81,12 +106,9 @@ + (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize + (FLTMediaSelectionOptions *)fromList:(NSArray *)list { FLTMediaSelectionOptions *pigeonResult = [[FLTMediaSelectionOptions alloc] init]; pigeonResult.maxSize = [FLTMaxSize nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - NSAssert(pigeonResult.maxSize != nil, @""); pigeonResult.imageQuality = GetNullableObjectAtIndex(list, 1); - pigeonResult.requestFullMetadata = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.requestFullMetadata != nil, @""); - pigeonResult.allowMultiple = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.allowMultiple != nil, @""); + pigeonResult.requestFullMetadata = [GetNullableObjectAtIndex(list, 2) boolValue]; + pigeonResult.allowMultiple = [GetNullableObjectAtIndex(list, 3) boolValue]; return pigeonResult; } + (nullable FLTMediaSelectionOptions *)nullableFromList:(NSArray *)list { @@ -95,9 +117,9 @@ + (nullable FLTMediaSelectionOptions *)nullableFromList:(NSArray *)list { - (NSArray *)toList { return @[ (self.maxSize ? [self.maxSize toList] : [NSNull null]), - (self.imageQuality ?: [NSNull null]), - (self.requestFullMetadata ?: [NSNull null]), - (self.allowMultiple ?: [NSNull null]), + self.imageQuality ?: [NSNull null], + @(self.requestFullMetadata), + @(self.allowMultiple), ]; } @end @@ -184,11 +206,11 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FLTImagePickerApiSetup(id binaryMessenger, +void SetUpFLTImagePickerApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickImage" + initWithName:@"dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { @@ -202,7 +224,7 @@ void FLTImagePickerApiSetup(id binaryMessenger, FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0); FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2); - NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 3); + BOOL arg_requestFullMetadata = [GetNullableObjectAtIndex(args, 3) boolValue]; [api pickImageWithSource:arg_source maxSize:arg_maxSize quality:arg_imageQuality @@ -217,7 +239,7 @@ void FLTImagePickerApiSetup(id binaryMessenger, } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickMultiImage" + initWithName:@"dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { @@ -230,7 +252,7 @@ void FLTImagePickerApiSetup(id binaryMessenger, NSArray *args = message; FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1); - NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2); + BOOL arg_requestFullMetadata = [GetNullableObjectAtIndex(args, 2) boolValue]; [api pickMultiImageWithMaxSize:arg_maxSize quality:arg_imageQuality fullMetadata:arg_requestFullMetadata @@ -245,7 +267,7 @@ void FLTImagePickerApiSetup(id binaryMessenger, } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickVideo" + initWithName:@"dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { @@ -270,7 +292,7 @@ void FLTImagePickerApiSetup(id binaryMessenger, /// Selects images and videos and returns their paths. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickMedia" + initWithName:@"dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart index d9ce3624c88a..0ab7d1b5186e 100644 --- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart +++ b/packages/image_picker/image_picker_ios/lib/src/messages.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 (v9.2.5), 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,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + enum SourceCamera { rear, front, @@ -86,17 +97,17 @@ class MediaSelectionOptions { class SourceSpecification { SourceSpecification({ required this.type, - this.camera, + required this.camera, }); SourceType type; - SourceCamera? camera; + SourceCamera camera; Object encode() { return [ type.index, - camera?.index, + camera.index, ]; } @@ -104,7 +115,7 @@ class SourceSpecification { result as List; return SourceSpecification( type: SourceType.values[result[0]! as int], - camera: result[1] != null ? SourceCamera.values[result[1]! as int] : null, + camera: SourceCamera.values[result[1]! as int], ); } } @@ -155,7 +166,7 @@ class ImagePickerApi { Future pickImage(SourceSpecification arg_source, MaxSize arg_maxSize, int? arg_imageQuality, bool arg_requestFullMetadata) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_source, @@ -182,7 +193,8 @@ class ImagePickerApi { Future> pickMultiImage(MaxSize arg_maxSize, int? arg_imageQuality, bool arg_requestFullMetadata) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( [arg_maxSize, arg_imageQuality, arg_requestFullMetadata]) @@ -211,7 +223,7 @@ class ImagePickerApi { Future pickVideo( SourceSpecification arg_source, int? arg_maxDurationSeconds) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImagePickerApi.pickVideo', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_source, arg_maxDurationSeconds]) as List?; @@ -235,7 +247,7 @@ class ImagePickerApi { Future> pickMedia( MediaSelectionOptions arg_mediaSelectionOptions) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_mediaSelectionOptions]) as List?; diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index be6b61a3fab2..0e69598304fe 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -43,7 +43,7 @@ enum SourceType { camera, gallery } class SourceSpecification { SourceSpecification(this.type, this.camera); SourceType type; - SourceCamera? camera; + SourceCamera camera; } @HostApi(dartHostTestHandler: 'TestHostImagePickerApi') diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 6b71c36d5be2..1de34ae7e7df 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.8+2 +version: 0.8.8+4 environment: sdk: ">=2.19.0 <4.0.0" @@ -25,7 +25,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^9.2.4 + pigeon: ^13.0.0 topics: - camera diff --git a/packages/image_picker/image_picker_ios/test/test_api.g.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart index ca1a7af01ad4..a2d02666baa0 100644 --- a/packages/image_picker/image_picker_ios/test/test_api.g.dart +++ b/packages/image_picker/image_picker_ios/test/test_api.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 (v9.2.5), 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 @@ -67,7 +67,7 @@ abstract class TestHostImagePickerApi { {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage', codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -77,28 +77,36 @@ abstract class TestHostImagePickerApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage was null.'); final List args = (message as List?)!; final SourceSpecification? arg_source = (args[0] as SourceSpecification?); assert(arg_source != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null SourceSpecification.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage was null, expected non-null SourceSpecification.'); final MaxSize? arg_maxSize = (args[1] as MaxSize?); assert(arg_maxSize != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null MaxSize.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage was null, expected non-null MaxSize.'); final int? arg_imageQuality = (args[2] as int?); final bool? arg_requestFullMetadata = (args[3] as bool?); assert(arg_requestFullMetadata != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null bool.'); - final String? output = await api.pickImage(arg_source!, arg_maxSize!, - arg_imageQuality, arg_requestFullMetadata!); - return [output]; + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage was null, expected non-null bool.'); + try { + final String? output = await api.pickImage(arg_source!, + arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); + return [output]; + } 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.ImagePickerApi.pickMultiImage', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage', + codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -108,24 +116,31 @@ abstract class TestHostImagePickerApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage was null.'); final List args = (message as List?)!; final MaxSize? arg_maxSize = (args[0] as MaxSize?); assert(arg_maxSize != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null MaxSize.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage was null, expected non-null MaxSize.'); final int? arg_imageQuality = (args[1] as int?); final bool? arg_requestFullMetadata = (args[2] as bool?); assert(arg_requestFullMetadata != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null bool.'); - final List output = await api.pickMultiImage( - arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); - return [output]; + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage was null, expected non-null bool.'); + try { + final List output = await api.pickMultiImage( + arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); + return [output]; + } 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.ImagePickerApi.pickVideo', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo', codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -135,22 +150,29 @@ abstract class TestHostImagePickerApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo was null.'); final List args = (message as List?)!; final SourceSpecification? arg_source = (args[0] as SourceSpecification?); assert(arg_source != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null, expected non-null SourceSpecification.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo was null, expected non-null SourceSpecification.'); final int? arg_maxDurationSeconds = (args[1] as int?); - final String? output = - await api.pickVideo(arg_source!, arg_maxDurationSeconds); - return [output]; + try { + final String? output = + await api.pickVideo(arg_source!, arg_maxDurationSeconds); + return [output]; + } 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.ImagePickerApi.pickMedia', codec, + 'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia', codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -160,15 +182,22 @@ abstract class TestHostImagePickerApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null.'); + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia was null.'); final List args = (message as List?)!; final MediaSelectionOptions? arg_mediaSelectionOptions = (args[0] as MediaSelectionOptions?); assert(arg_mediaSelectionOptions != null, - 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.'); - final List output = - await api.pickMedia(arg_mediaSelectionOptions!); - return [output]; + 'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.'); + try { + final List output = + await api.pickMedia(arg_mediaSelectionOptions!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } 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 8731c024079f..b0f7453a43f7 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:20230618' + testImplementation 'org.json:json:20231013' 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 88ddaca8ca0c..c627952952ba 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.3.0+15 + +* Adds missing network error response code to BillingResponse enum. + +## 0.3.0+14 + +* Updates annotations lib to 1.7.0. + ## 0.3.0+13 * Updates example code for current versions of Flutter. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index f658a58896bb..8482d3f3ea5b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -57,7 +57,7 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.6.0' + implementation 'androidx.annotation:annotation:1.7.0' // 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 implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22")) 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 8132c5b359da..80da270703a9 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 @@ -414,6 +414,10 @@ enum BillingResponse { /// Failure to consume since item is not owned. @JsonValue(8) itemNotOwned, + + /// Network connection failure between the device and Play systems. + @JsonValue(12) + networkError, } /// Serializer for [BillingResponse]. 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 7f636f034347..f0f99ee55255 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 @@ -19,6 +19,7 @@ const _$BillingResponseEnumMap = { BillingResponse.error: 6, BillingResponse.itemAlreadyOwned: 7, BillingResponse.itemNotOwned: 8, + BillingResponse.networkError: 12, }; const _$ProductTypeEnumMap = { 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 d9e4e09fb229..8902fb79f3ef 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.0+13 +version: 0.3.0+15 environment: sdk: ">=2.19.0 <4.0.0" 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 ad5a7112f4dd..97e3bc64c913 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 @@ -54,6 +54,7 @@ void main() { converter.fromJson(6); converter.fromJson(7); converter.fromJson(8); + converter.fromJson(12); }); group('startConnection', () { diff --git a/packages/local_auth/local_auth_ios/CHANGELOG.md b/packages/local_auth/local_auth_ios/CHANGELOG.md index e6575246262c..b04c99869fa0 100644 --- a/packages/local_auth/local_auth_ios/CHANGELOG.md +++ b/packages/local_auth/local_auth_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.5 + +* Updates to Pigeon 13. + ## 1.1.4 * Adds pub topics to package metadata. diff --git a/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m b/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m index dff4c4c11ad1..a2c969055cef 100644 --- a/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m +++ b/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m @@ -73,9 +73,9 @@ - (void)testSuccessfullAuthWithBiometrics { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@YES - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:YES + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -111,9 +111,9 @@ - (void)testSuccessfullAuthWithoutBiometrics { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -149,9 +149,9 @@ - (void)testFailedAuthWithBiometrics { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@YES - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:YES + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -191,9 +191,9 @@ - (void)testFailedWithUnknownErrorCode { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -229,9 +229,9 @@ - (void)testSystemCancelledWithoutStickyAuth { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -267,9 +267,9 @@ - (void)testFailedAuthWithoutBiometrics { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -310,9 +310,9 @@ - (void)testLocalizedFallbackTitle { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { @@ -348,9 +348,9 @@ - (void)testSkippedLocalizedFallbackTitle { .andDo(backgroundThreadReplyCaller); XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:@NO - sticky:@NO - useErrorDialogs:@NO] + [plugin authenticateWithOptions:[FLAAuthOptions makeWithBiometricOnly:NO + sticky:NO + useErrorDialogs:NO] strings:strings completion:^(FLAAuthResultDetails *_Nullable resultDetails, FlutterError *_Nullable error) { diff --git a/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m index ea105f3943ce..4962c39c396a 100644 --- a/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m @@ -60,7 +60,7 @@ @implementation FLTLocalAuthPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FLTLocalAuthPlugin *instance = [[FLTLocalAuthPlugin alloc] init]; [registrar addApplicationDelegate:instance]; - FLALocalAuthApiSetup([registrar messenger], instance); + SetUpFLALocalAuthApi([registrar messenger], instance); } - (instancetype)init { @@ -86,9 +86,8 @@ - (void)authenticateWithOptions:(nonnull FLAAuthOptions *)options self.lastCallState = nil; context.localizedFallbackTitle = strings.localizedFallbackTitle; - LAPolicy policy = options.biometricOnly.boolValue - ? LAPolicyDeviceOwnerAuthenticationWithBiometrics - : LAPolicyDeviceOwnerAuthentication; + LAPolicy policy = options.biometricOnly ? LAPolicyDeviceOwnerAuthenticationWithBiometrics + : LAPolicyDeviceOwnerAuthentication; if ([context canEvaluatePolicy:policy error:&authError]) { [context evaluatePolicy:policy localizedReason:strings.reason @@ -208,7 +207,7 @@ - (void)handleAuthReplyWithSuccess:(BOOL)success [self handleError:error withOptions:options strings:strings completion:completion]; return; case LAErrorSystemCancel: - if ([options.sticky boolValue]) { + if (options.sticky) { _lastCallState = [[FLAStickyAuthState alloc] initWithOptions:options strings:strings resultHandler:completion]; @@ -237,7 +236,7 @@ - (void)handleError:(NSError *)authError switch (authError.code) { case LAErrorPasscodeNotSet: case LAErrorBiometryNotEnrolled: - if (options.useErrorDialogs.boolValue) { + if (options.useErrorDialogs) { [self showAlertWithMessage:strings.goToSettingsDescription dismissButtonTitle:strings.cancelButton openSettingsButtonTitle:strings.goToSettingsButton diff --git a/packages/local_auth/local_auth_ios/ios/Classes/messages.g.h b/packages/local_auth/local_auth_ios/ios/Classes/messages.g.h index eda93cd93e6b..f8574c094fc6 100644 --- a/packages/local_auth/local_auth_ios/ios/Classes/messages.g.h +++ b/packages/local_auth/local_auth_ios/ios/Classes/messages.g.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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -27,12 +27,24 @@ typedef NS_ENUM(NSUInteger, FLAAuthResult) { FLAAuthResultErrorPasscodeNotSet = 4, }; +/// Wrapper for FLAAuthResult to allow for nullability. +@interface FLAAuthResultBox : NSObject +@property(nonatomic, assign) FLAAuthResult value; +- (instancetype)initWithValue:(FLAAuthResult)value; +@end + /// Pigeon equivalent of the subset of BiometricType used by iOS. typedef NS_ENUM(NSUInteger, FLAAuthBiometric) { FLAAuthBiometricFace = 0, FLAAuthBiometricFingerprint = 1, }; +/// Wrapper for FLAAuthBiometric to allow for nullability. +@interface FLAAuthBiometricBox : NSObject +@property(nonatomic, assign) FLAAuthBiometric value; +- (instancetype)initWithValue:(FLAAuthBiometric)value; +@end + @class FLAAuthStrings; @class FLAAuthOptions; @class FLAAuthResultDetails; @@ -61,12 +73,12 @@ typedef NS_ENUM(NSUInteger, FLAAuthBiometric) { @interface FLAAuthOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithBiometricOnly:(NSNumber *)biometricOnly - sticky:(NSNumber *)sticky - useErrorDialogs:(NSNumber *)useErrorDialogs; -@property(nonatomic, strong) NSNumber *biometricOnly; -@property(nonatomic, strong) NSNumber *sticky; -@property(nonatomic, strong) NSNumber *useErrorDialogs; ++ (instancetype)makeWithBiometricOnly:(BOOL)biometricOnly + sticky:(BOOL)sticky + useErrorDialogs:(BOOL)useErrorDialogs; +@property(nonatomic, assign) BOOL biometricOnly; +@property(nonatomic, assign) BOOL sticky; +@property(nonatomic, assign) BOOL useErrorDialogs; @end @interface FLAAuthResultDetails : NSObject @@ -117,7 +129,7 @@ NSObject *FLALocalAuthApiGetCodec(void); FlutterError *_Nullable))completion; @end -extern void FLALocalAuthApiSetup(id binaryMessenger, +extern void SetUpFLALocalAuthApi(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END diff --git a/packages/local_auth/local_auth_ios/ios/Classes/messages.g.m b/packages/local_auth/local_auth_ios/ios/Classes/messages.g.m index 550e7ada9e2f..5e58f7e1e398 100644 --- a/packages/local_auth/local_auth_ios/ios/Classes/messages.g.m +++ b/packages/local_auth/local_auth_ios/ios/Classes/messages.g.m @@ -1,16 +1,43 @@ // 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else #import +#endif #if !__has_feature(objc_arc) #error File requires ARC to be enabled. #endif +/// Possible outcomes of an authentication attempt. +@implementation FLAAuthResultBox +- (instancetype)initWithValue:(FLAAuthResult)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Pigeon equivalent of the subset of BiometricType used by iOS. +@implementation FLAAuthBiometricBox +- (instancetype)initWithValue:(FLAAuthBiometric)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ @@ -67,15 +94,10 @@ + (instancetype)makeWithReason:(NSString *)reason + (FLAAuthStrings *)fromList:(NSArray *)list { FLAAuthStrings *pigeonResult = [[FLAAuthStrings alloc] init]; pigeonResult.reason = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.reason != nil, @""); pigeonResult.lockOut = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.lockOut != nil, @""); pigeonResult.goToSettingsButton = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.goToSettingsButton != nil, @""); pigeonResult.goToSettingsDescription = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.goToSettingsDescription != nil, @""); pigeonResult.cancelButton = GetNullableObjectAtIndex(list, 4); - NSAssert(pigeonResult.cancelButton != nil, @""); pigeonResult.localizedFallbackTitle = GetNullableObjectAtIndex(list, 5); return pigeonResult; } @@ -84,20 +106,20 @@ + (nullable FLAAuthStrings *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.reason ?: [NSNull null]), - (self.lockOut ?: [NSNull null]), - (self.goToSettingsButton ?: [NSNull null]), - (self.goToSettingsDescription ?: [NSNull null]), - (self.cancelButton ?: [NSNull null]), - (self.localizedFallbackTitle ?: [NSNull null]), + self.reason ?: [NSNull null], + self.lockOut ?: [NSNull null], + self.goToSettingsButton ?: [NSNull null], + self.goToSettingsDescription ?: [NSNull null], + self.cancelButton ?: [NSNull null], + self.localizedFallbackTitle ?: [NSNull null], ]; } @end @implementation FLAAuthOptions -+ (instancetype)makeWithBiometricOnly:(NSNumber *)biometricOnly - sticky:(NSNumber *)sticky - useErrorDialogs:(NSNumber *)useErrorDialogs { ++ (instancetype)makeWithBiometricOnly:(BOOL)biometricOnly + sticky:(BOOL)sticky + useErrorDialogs:(BOOL)useErrorDialogs { FLAAuthOptions *pigeonResult = [[FLAAuthOptions alloc] init]; pigeonResult.biometricOnly = biometricOnly; pigeonResult.sticky = sticky; @@ -106,12 +128,9 @@ + (instancetype)makeWithBiometricOnly:(NSNumber *)biometricOnly } + (FLAAuthOptions *)fromList:(NSArray *)list { FLAAuthOptions *pigeonResult = [[FLAAuthOptions alloc] init]; - pigeonResult.biometricOnly = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.biometricOnly != nil, @""); - pigeonResult.sticky = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.sticky != nil, @""); - pigeonResult.useErrorDialogs = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.useErrorDialogs != nil, @""); + pigeonResult.biometricOnly = [GetNullableObjectAtIndex(list, 0) boolValue]; + pigeonResult.sticky = [GetNullableObjectAtIndex(list, 1) boolValue]; + pigeonResult.useErrorDialogs = [GetNullableObjectAtIndex(list, 2) boolValue]; return pigeonResult; } + (nullable FLAAuthOptions *)nullableFromList:(NSArray *)list { @@ -119,9 +138,9 @@ + (nullable FLAAuthOptions *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.biometricOnly ?: [NSNull null]), - (self.sticky ?: [NSNull null]), - (self.useErrorDialogs ?: [NSNull null]), + @(self.biometricOnly), + @(self.sticky), + @(self.useErrorDialogs), ]; } @end @@ -149,8 +168,8 @@ + (nullable FLAAuthResultDetails *)nullableFromList:(NSArray *)list { - (NSArray *)toList { return @[ @(self.result), - (self.errorMessage ?: [NSNull null]), - (self.errorDetails ?: [NSNull null]), + self.errorMessage ?: [NSNull null], + self.errorDetails ?: [NSNull null], ]; } @end @@ -239,12 +258,12 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FLALocalAuthApiSetup(id binaryMessenger, +void SetUpFLALocalAuthApi(id binaryMessenger, NSObject *api) { /// Returns true if this device supports authentication. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.LocalAuthApi.isDeviceSupported" + initWithName:@"dev.flutter.pigeon.local_auth_ios.LocalAuthApi.isDeviceSupported" binaryMessenger:binaryMessenger codec:FLALocalAuthApiGetCodec()]; if (api) { @@ -265,7 +284,7 @@ void FLALocalAuthApiSetup(id binaryMessenger, /// any biometrics are enrolled or not. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.LocalAuthApi.deviceCanSupportBiometrics" + initWithName:@"dev.flutter.pigeon.local_auth_ios.LocalAuthApi.deviceCanSupportBiometrics" binaryMessenger:binaryMessenger codec:FLALocalAuthApiGetCodec()]; if (api) { @@ -286,7 +305,7 @@ void FLALocalAuthApiSetup(id binaryMessenger, /// without additional setup. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.LocalAuthApi.getEnrolledBiometrics" + initWithName:@"dev.flutter.pigeon.local_auth_ios.LocalAuthApi.getEnrolledBiometrics" binaryMessenger:binaryMessenger codec:FLALocalAuthApiGetCodec()]; if (api) { @@ -307,7 +326,7 @@ void FLALocalAuthApiSetup(id binaryMessenger, /// [strings] for any UI. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.LocalAuthApi.authenticate" + initWithName:@"dev.flutter.pigeon.local_auth_ios.LocalAuthApi.authenticate" binaryMessenger:binaryMessenger codec:FLALocalAuthApiGetCodec()]; if (api) { diff --git a/packages/local_auth/local_auth_ios/lib/src/messages.g.dart b/packages/local_auth/local_auth_ios/lib/src/messages.g.dart index 48ab4e53232f..cc93815a25ca 100644 --- a/packages/local_auth/local_auth_ios/lib/src/messages.g.dart +++ b/packages/local_auth/local_auth_ios/lib/src/messages.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 (v9.2.5), 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,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + /// Possible outcomes of an authentication attempt. enum AuthResult { /// The user authenticated successfully. @@ -221,7 +232,8 @@ class LocalAuthApi { /// Returns true if this device supports authentication. Future isDeviceSupported() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.LocalAuthApi.isDeviceSupported', codec, + 'dev.flutter.pigeon.local_auth_ios.LocalAuthApi.isDeviceSupported', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { @@ -249,7 +261,8 @@ class LocalAuthApi { /// any biometrics are enrolled or not. Future deviceCanSupportBiometrics() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.LocalAuthApi.deviceCanSupportBiometrics', codec, + 'dev.flutter.pigeon.local_auth_ios.LocalAuthApi.deviceCanSupportBiometrics', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { @@ -277,7 +290,8 @@ class LocalAuthApi { /// without additional setup. Future> getEnrolledBiometrics() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.LocalAuthApi.getEnrolledBiometrics', codec, + 'dev.flutter.pigeon.local_auth_ios.LocalAuthApi.getEnrolledBiometrics', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { @@ -306,7 +320,7 @@ class LocalAuthApi { Future authenticate( AuthOptions arg_options, AuthStrings arg_strings) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.LocalAuthApi.authenticate', codec, + 'dev.flutter.pigeon.local_auth_ios.LocalAuthApi.authenticate', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_options, arg_strings]) as List?; diff --git a/packages/local_auth/local_auth_ios/pubspec.yaml b/packages/local_auth/local_auth_ios/pubspec.yaml index 2e336117eb60..a4fcff5b55d7 100644 --- a/packages/local_auth/local_auth_ios/pubspec.yaml +++ b/packages/local_auth/local_auth_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_ios description: iOS implementation of the local_auth plugin. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.1.4 +version: 1.1.5 environment: sdk: ">=3.0.0 <4.0.0" @@ -27,7 +27,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^9.2.4 + pigeon: ^13.0.0 topics: - authentication diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md index 407f1504d697..7e271c081d91 100644 --- a/packages/path_provider/path_provider_android/CHANGELOG.md +++ b/packages/path_provider/path_provider_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.1 + +* Updates annotations lib to 1.7.0. + ## 2.2.0 * Adds implementation of `getDownloadsDirectory()`. diff --git a/packages/path_provider/path_provider_android/android/build.gradle b/packages/path_provider/path_provider_android/android/build.gradle index 0cdb3d00eaed..6f75c3c8be14 100644 --- a/packages/path_provider/path_provider_android/android/build.gradle +++ b/packages/path_provider/path_provider_android/android/build.gradle @@ -57,6 +57,6 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.5.0' + implementation 'androidx.annotation:annotation:1.7.0' testImplementation 'junit:junit:4.13.2' } diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml index 1e8a84919896..4308c30c1d4a 100644 --- a/packages/path_provider/path_provider_android/pubspec.yaml +++ b/packages/path_provider/path_provider_android/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider_android description: Android implementation of the path_provider plugin. repository: https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.2.0 +version: 2.2.1 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index daab8da8bcb4..bac4e049fba4 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,22 @@ +## 13.0.0 + +* **Breaking Change** [objc] Eliminates boxing of non-nullable primitive types + (bool, int, double). Changes required: + * Implementations of host API methods that take non-nullable + primitives will need to be updated to match the new signatures. + * Calls to Flutter API methods that take non-nullable primitives will need to + be updated to pass unboxed values. + * Calls to non-nullable primitive property methods on generated data classes + will need to be updated. + * **WARNING**: Current versions of `Xcode` do not appear to warn about + implicit `NSNumber *` to `BOOL` conversions, so code that is no longer + correct after this breaking change may compile without warning. For example, + `myGeneratedClass.aBoolProperty = @NO` can silently set `aBoolProperty` to + `YES`. Any data class or Flutter API interactions involving `bool`s should + be carefully audited by hand when updating. + + + ## 12.0.1 * [swift] Adds protocol for Flutter APIs. diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h index 834fc1bc5d02..bba1ff2deb07 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.h +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -36,7 +36,7 @@ typedef NS_ENUM(NSUInteger, PGNCode) { @property(nonatomic, copy, nullable) NSString *name; @property(nonatomic, copy, nullable) NSString *description; @property(nonatomic, assign) PGNCode code; -@property(nonatomic, strong) NSDictionary *data; +@property(nonatomic, copy) NSDictionary *data; @end /// The codec used by PGNExampleHostApi. @@ -46,8 +46,8 @@ NSObject *PGNExampleHostApiGetCodec(void); /// @return `nil` only when `error != nil`. - (nullable NSString *)getHostLanguageWithError:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)addNumber:(NSNumber *)a - toNumber:(NSNumber *)b +- (nullable NSNumber *)addNumber:(NSInteger)a + toNumber:(NSInteger)b error:(FlutterError *_Nullable *_Nonnull)error; - (void)sendMessageMessage:(PGNMessageData *)message completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.m b/packages/pigeon/example/app/macos/Runner/messages.g.m index 45c7058736ae..6861a6b7d6fa 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.m +++ b/packages/pigeon/example/app/macos/Runner/messages.g.m @@ -63,7 +63,6 @@ + (PGNMessageData *)fromList:(NSArray *)list { pigeonResult.description = GetNullableObjectAtIndex(list, 1); pigeonResult.code = [GetNullableObjectAtIndex(list, 2) integerValue]; pigeonResult.data = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.data != nil, @""); return pigeonResult; } + (nullable PGNMessageData *)nullableFromList:(NSArray *)list { @@ -71,10 +70,10 @@ + (nullable PGNMessageData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.name ?: [NSNull null]), - (self.description ?: [NSNull null]), + self.name ?: [NSNull null], + self.description ?: [NSNull null], @(self.code), - (self.data ?: [NSNull null]), + self.data ?: [NSNull null], ]; } @end @@ -160,8 +159,8 @@ void SetUpPGNExampleHostApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_a = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_b = GetNullableObjectAtIndex(args, 1); + NSInteger arg_a = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_b = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; NSNumber *output = [api addNumber:arg_a toNumber:arg_b error:&error]; callback(wrapResult(output, error)); diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index c2d2bfad607c..e0120ab316c0 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 = '12.0.1'; +const String pigeonVersion = '13.0.0'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 2638a0f0ef6e..ccae81868675 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -232,19 +232,17 @@ class ObjcHeaderGenerator extends StructuredGenerator { field, classes, enums, - (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x), + (TypeDeclaration x) => _objcTypeStringForPrimitiveDartType(prefix, x, + beforeString: true), customResolver: customEnumNames.contains(field.type.baseName) ? (String x) => _enumName(x, prefix: prefix) : (String x) => '${_className(prefix, x)} *'); late final String propertyType; addDocumentationComments( indent, field.documentationComments, _docCommentSpec); - if (customEnumNames.contains(field.type.baseName) && - !field.type.isNullable) { - propertyType = 'assign'; - } else { - propertyType = _propertyTypeForDartType(field); - } + propertyType = _propertyTypeForDartType(field.type, + isNullable: field.type.isNullable, + isEnum: customEnumNames.contains(field.type.baseName)); final String nullability = field.type.isNullable ? ', nullable' : ''; final String fieldType = isEnum(root, field.type) && field.type.isNullable ? _enumName(field.type.baseName, @@ -314,8 +312,11 @@ class ObjcHeaderGenerator extends StructuredGenerator { indent.writeln( '- (instancetype)initWithBinaryMessenger:(id)binaryMessenger;'); for (final Method func in api.methods) { - final _ObjcPtr returnType = - _objcTypeForDartType(generatorOptions.prefix, func.returnType); + final _ObjcType returnType = _objcTypeForDartType( + generatorOptions.prefix, func.returnType, + // Nullability is required since the return must be nil if NSError is set. + forceNullability: true, + ); final String callbackType = _callbackForType(root, func.returnType, returnType, generatorOptions); addDocumentationComments( @@ -354,8 +355,13 @@ class ObjcHeaderGenerator extends StructuredGenerator { indent.writeln('@protocol $apiName'); for (final Method func in api.methods) { - final _ObjcPtr returnTypeName = - _objcTypeForDartType(generatorOptions.prefix, func.returnType); + final _ObjcType returnTypeName = _objcTypeForDartType( + generatorOptions.prefix, + func.returnType, + // Nullability is required since the return must be nil if NSError is + // set. + forceNullability: true, + ); String? lastArgName; String? lastArgType; @@ -375,7 +381,7 @@ class ObjcHeaderGenerator extends StructuredGenerator { lastArgType = 'void (^)($enumReturnType, FlutterError *_Nullable)'; } else { lastArgType = - 'void (^)(${returnTypeName.withPtr}_Nullable, FlutterError *_Nullable)'; + 'void (^)(${returnTypeName.beforeString}_Nullable, FlutterError *_Nullable)'; } } else { if (func.returnType.isVoid) { @@ -383,7 +389,7 @@ class ObjcHeaderGenerator extends StructuredGenerator { } else if (isEnum(root, func.returnType)) { returnType = enumReturnType; } else { - returnType = 'nullable ${returnTypeName.withPtr.trim()}'; + returnType = 'nullable $returnTypeName'; } lastArgType = 'FlutterError *_Nullable *_Nonnull'; @@ -588,24 +594,23 @@ class ObjcSourceGenerator extends StructuredGenerator { indent.writeln('$className *$resultName = [[$className alloc] init];'); enumerate(getFieldsInSerializationOrder(klass), (int index, final NamedType field) { - if (customEnumNames.contains(field.type.baseName)) { - if (field.type.isNullable) { - indent.writeln( - 'NSNumber *${field.name}AsNumber = GetNullableObjectAtIndex(list, $index);'); - indent.writeln( - '${_enumName(field.type.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)}${field.name} = ${field.name}AsNumber == nil ? nil : [[${_enumName(field.type.baseName, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[${field.name}AsNumber integerValue]];'); - indent.writeln('$resultName.${field.name} = ${field.name};'); - } else { - indent.writeln( - '$resultName.${field.name} = [${_listGetter(customClassNames, 'list', field, index, generatorOptions.prefix)} integerValue];'); - } - } else { + final bool isEnumType = customEnumNames.contains(field.type.baseName); + final String valueGetter = _listGetter( + customClassNames, 'list', field, index, generatorOptions.prefix); + final String? primitiveExtractionMethod = + _nsnumberExtractionMethod(field.type, isEnum: isEnumType); + final String ivarValueExpression; + if (primitiveExtractionMethod != null) { + ivarValueExpression = '[$valueGetter $primitiveExtractionMethod]'; + } else if (isEnumType) { + indent.writeln('NSNumber *${field.name}AsNumber = $valueGetter;'); indent.writeln( - '$resultName.${field.name} = ${_listGetter(customClassNames, 'list', field, index, generatorOptions.prefix)};'); - if (!field.type.isNullable) { - indent.writeln('NSAssert($resultName.${field.name} != nil, @"");'); - } + '${_enumName(field.type.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)}${field.name} = ${field.name}AsNumber == nil ? nil : [[${_enumName(field.type.baseName, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[${field.name}AsNumber integerValue]];'); + ivarValueExpression = field.name; + } else { + ivarValueExpression = valueGetter; } + indent.writeln('$resultName.${field.name} = $ivarValueExpression;'); }); indent.writeln('return $resultName;'); }); @@ -717,30 +722,29 @@ class ObjcSourceGenerator extends StructuredGenerator { int count = 0; for (final NamedType arg in func.arguments) { final String argName = _getSafeArgName(count, arg); - if (isEnum(root, arg.type)) { - final String className = - _className(generatorOptions.prefix, arg.type.baseName); - if (arg.type.isNullable) { - indent.writeln( - 'NSNumber *${argName}AsNumber = GetNullableObjectAtIndex(args, $count);'); - 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]];'); - } else { - indent.writeln( - '$className $argName = [GetNullableObjectAtIndex(args, $count) integerValue];'); - } - } else { - final _ObjcPtr argType = - _objcTypeForDartType(generatorOptions.prefix, arg.type); + final bool isEnumType = isEnum(root, arg.type); + final String valueGetter = 'GetNullableObjectAtIndex(args, $count)'; + final String? primitiveExtractionMethod = + _nsnumberExtractionMethod(arg.type, isEnum: isEnumType); + final _ObjcType objcArgType = _objcTypeForDartType( + generatorOptions.prefix, arg.type, + isEnum: isEnumType); + if (primitiveExtractionMethod != null) { indent.writeln( - '${argType.withPtr}$argName = GetNullableObjectAtIndex(args, $count);'); + '${objcArgType.beforeString}$argName = [$valueGetter $primitiveExtractionMethod];'); + } else if (isEnumType) { + 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]];'); + } else { + indent.writeln('${objcArgType.beforeString}$argName = $valueGetter;'); } count++; } } void writeAsyncBindings(Iterable selectorComponents, - String callSignature, _ObjcPtr returnType) { + String callSignature, _ObjcType returnType) { if (func.returnType.isVoid) { const String callback = 'callback(wrapResult(nil, error));'; if (func.arguments.isEmpty) { @@ -758,7 +762,7 @@ class ObjcSourceGenerator extends StructuredGenerator { } } else { const String callback = 'callback(wrapResult(output, error));'; - String returnTypeString = '${returnType.withPtr}_Nullable output'; + String returnTypeString = '${returnType.beforeString}_Nullable output'; const String numberOutput = 'NSNumber *output ='; const String enumConversionExpression = 'enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value];'; @@ -789,7 +793,7 @@ class ObjcSourceGenerator extends StructuredGenerator { } } - void writeSyncBindings(String call, _ObjcPtr returnType) { + void writeSyncBindings(String call, _ObjcType returnType) { indent.writeln('FlutterError *error;'); if (func.returnType.isVoid) { indent.writeln('$call;'); @@ -801,7 +805,7 @@ class ObjcSourceGenerator extends StructuredGenerator { indent.writeln( 'NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value];'); } else { - indent.writeln('${returnType.withPtr}output = $call;'); + indent.writeln('${returnType.beforeString}output = $call;'); } indent.writeln('callback(wrapResult(output, error));'); } @@ -816,8 +820,11 @@ class ObjcSourceGenerator extends StructuredGenerator { indent.write( '[$channel setMessageHandler:^(id _Nullable message, FlutterReply callback) '); indent.addScoped('{', '}];', () { - final _ObjcPtr returnType = - _objcTypeForDartType(generatorOptions.prefix, func.returnType); + final _ObjcType returnType = _objcTypeForDartType( + generatorOptions.prefix, func.returnType, + // Nullability is required since the return must be nil if NSError is set. + forceNullability: true, + ); final Iterable selectorComponents = _getSelectorComponents(func, lastSelectorComponent); final Iterable argNames = @@ -1042,8 +1049,12 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { Method func, { required String dartPackageName, }) { - final _ObjcPtr returnType = - _objcTypeForDartType(languageOptions.prefix, func.returnType); + final _ObjcType returnType = _objcTypeForDartType( + languageOptions.prefix, + func.returnType, + // Nullability is required since the return must be nil if NSError is set. + forceNullability: true, + ); final String callbackType = _callbackForType(root, func.returnType, returnType, languageOptions); @@ -1054,14 +1065,16 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { } else { int count = 0; String makeVarOrNSNullExpression(NamedType arg) { - String varExpression = '${argNameFunc(count, arg)} ?: [NSNull null]'; - if (isEnum(root, arg.type)) { + final String argName = argNameFunc(count, arg); + final bool isEnumType = isEnum(root, arg.type); + String varExpression = + _collectionSafeExpression(argName, arg.type, isEnum: isEnumType); + if (isEnumType) { if (arg.type.isNullable) { varExpression = - '${argNameFunc(count, arg)} == nil ? [NSNull null] : [NSNumber numberWithInteger:${argNameFunc(count, arg)}.value]'; + '${argNameFunc(count, arg)} == nil ? [NSNull null] : [NSNumber numberWithInteger:$argName.value]'; } else { - varExpression = - '[NSNumber numberWithInteger: ${argNameFunc(count, arg)}]'; + varExpression = '[NSNumber numberWithInteger:$argName]'; } } count++; @@ -1116,7 +1129,8 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { indent.writeln( '$enumName *output = outputAsNumber == nil ? nil : [[$enumName alloc] initWithValue:[outputAsNumber integerValue]];'); } else { - indent.writeln('${returnType.withPtr}output = $nullCheck;'); + indent + .writeln('${returnType.beforeString}output = $nullCheck;'); } indent.writeln('completion(output, nil);'); } @@ -1159,7 +1173,8 @@ void _writeObjcSourceClassInitializerDeclaration( field, classes, enums, - (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x), + (TypeDeclaration x) => _objcTypeStringForPrimitiveDartType(prefix, x, + beforeString: true), customResolver: customEnumNames.contains(field.type.baseName) ? (String x) => field.type.isNullable ? _enumName(x, suffix: ' *', prefix: prefix, box: true) @@ -1186,92 +1201,183 @@ String _className(String? prefix, String className) { /// Calculates callback block signature for async methods. String _callbackForType( - Root root, TypeDeclaration type, _ObjcPtr objcType, ObjcOptions options) { + Root root, TypeDeclaration type, _ObjcType objcType, ObjcOptions options) { if (type.isVoid) { return 'void (^)(FlutterError *_Nullable)'; } else if (isEnum(root, type)) { return 'void (^)(${_enumName(objcType.baseName, suffix: ' *_Nullable', prefix: options.prefix, box: true)}, FlutterError *_Nullable)'; } else { - return 'void (^)(${objcType.withPtr}_Nullable, FlutterError *_Nullable)'; + return 'void (^)(${objcType.beforeString}_Nullable, FlutterError *_Nullable)'; } } -/// Represents an ObjC pointer (ex 'id', 'NSString *'). -class _ObjcPtr { - const _ObjcPtr({required this.baseName}) : hasAsterisk = baseName != 'id'; +/// Represents an Objective-C type, including pointer (id, NSString*, etc.) and +/// primitive (BOOL, NSInteger, etc.) types. +class _ObjcType { + const _ObjcType({required this.baseName, bool isPointer = true}) + : hasAsterisk = isPointer && baseName != 'id'; final String baseName; final bool hasAsterisk; - String get withPtr => '$baseName${hasAsterisk ? ' *' : ' '}'; - String get ptr => hasAsterisk ? '*' : ''; + + @override + String toString() => hasAsterisk ? '$baseName *' : baseName; + + /// Returns a version of the string form that can be used directly before + /// another string (e.g., a variable name) and handle spacing correctly for + /// a right-aligned pointer format. + String get beforeString => hasAsterisk ? toString() : '$this '; } /// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *'). -const Map _objcTypeForDartTypeMap = { - 'bool': _ObjcPtr(baseName: 'NSNumber'), - 'int': _ObjcPtr(baseName: 'NSNumber'), - 'String': _ObjcPtr(baseName: 'NSString'), - 'double': _ObjcPtr(baseName: 'NSNumber'), - 'Uint8List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Int32List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Int64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Float64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'List': _ObjcPtr(baseName: 'NSArray'), - 'Map': _ObjcPtr(baseName: 'NSDictionary'), - 'Object': _ObjcPtr(baseName: 'id'), +const Map _objcTypeForNullableDartTypeMap = + { + 'bool': _ObjcType(baseName: 'NSNumber'), + 'int': _ObjcType(baseName: 'NSNumber'), + 'String': _ObjcType(baseName: 'NSString'), + 'double': _ObjcType(baseName: 'NSNumber'), + 'Uint8List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Int32List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Int64List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Float64List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'List': _ObjcType(baseName: 'NSArray'), + 'Map': _ObjcType(baseName: 'NSDictionary'), + 'Object': _ObjcType(baseName: 'id'), }; +/// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *'). +const Map _objcTypeForNonNullableDartTypeMap = + { + 'bool': _ObjcType(baseName: 'BOOL', isPointer: false), + 'int': _ObjcType(baseName: 'NSInteger', isPointer: false), + 'String': _ObjcType(baseName: 'NSString'), + 'double': _ObjcType(baseName: 'double', isPointer: false), + 'Uint8List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Int32List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Int64List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'Float64List': _ObjcType(baseName: 'FlutterStandardTypedData'), + 'List': _ObjcType(baseName: 'NSArray'), + 'Map': _ObjcType(baseName: 'NSDictionary'), + 'Object': _ObjcType(baseName: 'id'), +}; + +bool _usesPrimitive(TypeDeclaration type, {required bool isEnum}) { + // Only non-nullable types are unboxed. + if (!type.isNullable) { + if (isEnum) { + return true; + } + switch (type.baseName) { + case 'bool': + case 'int': + case 'double': + return true; + } + } + return false; +} + +String _collectionSafeExpression(String expression, TypeDeclaration type, + {required bool isEnum}) { + return _usesPrimitive(type, isEnum: isEnum) + ? '@($expression)' + : '$expression ?: [NSNull null]'; +} + +/// Returns the method to convert [type] from a boxed NSNumber to its +/// corresponding primitive value, if any. +String? _nsnumberExtractionMethod(TypeDeclaration type, + {required bool isEnum}) { + // Only non-nullable types are unboxed. + if (!type.isNullable) { + if (isEnum) { + return 'integerValue'; + } + switch (type.baseName) { + case 'bool': + return 'boolValue'; + case 'int': + return 'integerValue'; + case 'double': + return 'doubleValue'; + } + } + return null; +} + /// Converts list of [TypeDeclaration] to a code string representing the type /// arguments for use in generics. /// Example: ('FOO', ['Foo', 'Bar']) -> 'FOOFoo *, FOOBar *'). String _flattenTypeArguments(String? classPrefix, List args) { final String result = args .map((TypeDeclaration e) => - _objcTypeForDartType(classPrefix, e).withPtr.trim()) + _objcTypeForDartType(classPrefix, e).toString()) .join(', '); return result; } -String? _objcTypePtrForPrimitiveDartType( - String? classPrefix, TypeDeclaration type) { - return _objcTypeForDartTypeMap.containsKey(type.baseName) - ? _objcTypeForDartType(classPrefix, type).withPtr - : null; +_ObjcType? _objcTypeForPrimitiveDartType(TypeDeclaration type, + {bool forceNullability = false}) { + return forceNullability || type.isNullable + ? _objcTypeForNullableDartTypeMap[type.baseName] + : _objcTypeForNonNullableDartTypeMap[type.baseName]; +} + +String? _objcTypeStringForPrimitiveDartType( + String? classPrefix, TypeDeclaration type, + {required bool beforeString, bool forceNullability = false}) { + final _ObjcType? objcType; + if (forceNullability || type.isNullable) { + objcType = _objcTypeForNullableDartTypeMap.containsKey(type.baseName) + ? _objcTypeForDartType(classPrefix, type) + : null; + } else { + objcType = _objcTypeForNonNullableDartTypeMap.containsKey(type.baseName) + ? _objcTypeForDartType(classPrefix, type) + : null; + } + return beforeString ? objcType?.beforeString : objcType?.toString(); } -/// Returns the objc type for a dart [type], prepending the [classPrefix] for -/// generated classes. For example: -/// _objcTypeForDartType(null, 'int') => 'NSNumber'. -_ObjcPtr _objcTypeForDartType(String? classPrefix, TypeDeclaration field) { - return _objcTypeForDartTypeMap.containsKey(field.baseName) - ? field.typeArguments.isEmpty - ? _objcTypeForDartTypeMap[field.baseName]! - : _ObjcPtr( +/// Returns the Objective-C type for a Dart [field], prepending the +/// [classPrefix] for generated classes. +_ObjcType _objcTypeForDartType(String? classPrefix, TypeDeclaration field, + {bool isEnum = false, bool forceNullability = false}) { + final _ObjcType? primitiveType = + _objcTypeForPrimitiveDartType(field, forceNullability: forceNullability); + return primitiveType == null + ? _ObjcType( + baseName: _className(classPrefix, field.baseName), + // Non-nullable enums are non-pointer types. + isPointer: !isEnum || (field.isNullable || forceNullability)) + : field.typeArguments.isEmpty + ? primitiveType + : _ObjcType( baseName: - '${_objcTypeForDartTypeMap[field.baseName]!.baseName}<${_flattenTypeArguments(classPrefix, field.typeArguments)}>') - : _ObjcPtr(baseName: _className(classPrefix, field.baseName)); + '${primitiveType.baseName}<${_flattenTypeArguments(classPrefix, field.typeArguments)}>'); } /// Maps a type to a properties memory semantics (ie strong, copy). -String _propertyTypeForDartType(NamedType field) { - const Map propertyTypeForDartTypeMap = { - 'String': 'copy', - 'bool': 'strong', - 'int': 'strong', - 'double': 'strong', - 'Uint8List': 'strong', - 'Int32List': 'strong', - 'Int64List': 'strong', - 'Float64List': 'strong', - 'List': 'strong', - 'Map': 'strong', - }; - - final String? result = propertyTypeForDartTypeMap[field.type.baseName]; - if (result == null) { - return 'strong'; - } else { - return result; +String _propertyTypeForDartType(TypeDeclaration type, + {required bool isNullable, required bool isEnum}) { + if (isEnum) { + // Only the nullable versions are objects. + return isNullable ? 'strong' : 'assign'; + } + switch (type.baseName) { + case 'List': + case 'Map': + case 'String': + // Standard Objective-C practice is to copy strings and collections to + // avoid unexpected mutation if set to mutable versions. + return 'copy'; + case 'double': + case 'bool': + case 'int': + // Only the nullable versions are objects. + return isNullable ? 'strong' : 'assign'; } + // Anything else is a standard object, and should therefore be strong. + return 'strong'; } /// Generates the name of the codec that will be generated. @@ -1348,8 +1454,9 @@ String _makeObjcSignature({ return '${arg.type.isNullable ? 'nullable ' : ''}${_enumName(arg.type.baseName, suffix: arg.type.isNullable ? ' *' : '', prefix: options.prefix, box: arg.type.isNullable)}'; } else { final String nullable = arg.type.isNullable ? 'nullable ' : ''; - final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type); - return '$nullable${argType.withPtr.trim()}'; + final _ObjcType argType = + _objcTypeForDartType(options.prefix, arg.type); + return '$nullable$argType'; } }), lastArgType, @@ -1384,15 +1491,17 @@ String _listGetter(Set customClassNames, String list, NamedType field, String _arrayValue(Set customClassNames, Set customEnumNames, NamedType field) { + final bool isEnumType = customEnumNames.contains(field.type.baseName); if (customClassNames.contains(field.type.baseName)) { return '(self.${field.name} ? [self.${field.name} toList] : [NSNull null])'; - } else if (customEnumNames.contains(field.type.baseName)) { + } else if (isEnumType) { if (field.type.isNullable) { return '(self.${field.name} == nil ? [NSNull null] : [NSNumber numberWithInteger:self.${field.name}.value])'; } return '@(self.${field.name})'; } else { - return '(self.${field.name} ?: [NSNull null])'; + return _collectionSafeExpression('self.${field.name}', field.type, + isEnum: isEnumType); } } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/MultipleArityTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/MultipleArityTest.m index 5161c1859627..6e0541554fb0 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/MultipleArityTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/MultipleArityTest.m @@ -25,8 +25,8 @@ - (void)testSimple { MultipleArityFlutterApi *api = [[MultipleArityFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"subtraction"]; - [api subtractX:@(30) - y:@(10) + [api subtractX:30 + y:10 completion:^(NSNumber *_Nonnull result, FlutterError *_Nullable error) { XCTAssertNil(error); XCTAssertEqual(20, result.intValue); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullFieldsTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullFieldsTest.m index 04f9c7d3d1b3..e4032dc4cb5b 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullFieldsTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullFieldsTest.m @@ -29,7 +29,7 @@ @interface NullFieldsTest : XCTestCase @implementation NullFieldsTest - (void)testMakeWithValues { - NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:@"hello" identifier:@1]; + NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:@"hello" identifier:1]; NullFieldsSearchReplyTypeBox *typeWrapper = [[NullFieldsSearchReplyTypeBox alloc] initWithValue:NullFieldsSearchReplyTypeSuccess]; @@ -48,7 +48,7 @@ - (void)testMakeWithValues { } - (void)testMakeRequestWithNulls { - NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:nil identifier:@1]; + NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:nil identifier:1]; XCTAssertNil(request.query); } @@ -121,13 +121,13 @@ - (void)testReplyFromListWithNulls { } - (void)testRequestToListWithValuess { - NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:@"hello" identifier:@1]; + NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:@"hello" identifier:1]; NSArray *list = [request toList]; XCTAssertEqual(@"hello", list[0]); } - (void)testRequestToListWithNulls { - NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:nil identifier:@1]; + NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:nil identifier:1]; NSArray *list = [request toList]; XCTAssertEqual([NSNull null], list[0]); } @@ -139,7 +139,7 @@ - (void)testReplyToListWithValuess { makeWithResult:@"result" error:@"error" indices:@[ @1, @2, @3 ] - request:[NullFieldsSearchRequest makeWithQuery:@"hello" identifier:@1] + request:[NullFieldsSearchRequest makeWithQuery:@"hello" identifier:1] type:typeWrapper]; NSArray *list = [reply toList]; NSArray *indices = @[ @1, @2, @3 ]; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/PrimitiveTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/PrimitiveTest.m index ec109458bd0a..2c6b7af178f9 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/PrimitiveTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/PrimitiveTest.m @@ -21,7 +21,7 @@ - (void)testIntPrimitive { [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - [api anIntValue:@1 + [api anIntValue:1 completion:^(NSNumber *_Nonnull result, FlutterError *_Nullable err) { XCTAssertEqualObjects(@1, result); [expectation fulfill]; @@ -34,10 +34,11 @@ - (void)testBoolPrimitive { [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - NSNumber *arg = @YES; + BOOL arg = YES; [api aBoolValue:arg completion:^(NSNumber *_Nonnull result, FlutterError *_Nullable err) { - XCTAssertEqualObjects(arg, result); + XCTAssertNotNil(result); + XCTAssertEqual(arg, result.boolValue); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:1.0]; @@ -48,10 +49,11 @@ - (void)testDoublePrimitive { [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - NSNumber *arg = @(1.5); + NSInteger arg = 1.5; [api aDoubleValue:arg completion:^(NSNumber *_Nonnull result, FlutterError *_Nullable err) { - XCTAssertEqualObjects(arg, result); + XCTAssertNotNil(result); + XCTAssertEqual(arg, result.integerValue); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:1.0]; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/project.pbxproj index 7bfbd26eff5b..27dfc3decfbb 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/project.pbxproj @@ -259,7 +259,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a485857be2f2..fbd9258fad08 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ *> *nullableNestedList; -@property(nonatomic, strong, nullable) +@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, strong, nullable) NSDictionary *nullableMapWithObject; +@property(nonatomic, copy, nullable) NSDictionary *nullableMapWithObject; @property(nonatomic, strong, nullable) AnEnumBox *aNullableEnum; @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; @@ -118,7 +118,7 @@ typedef NS_ENUM(NSUInteger, AnEnum) { /// A data class containing a List, used in unit tests. @interface TestMessage : NSObject + (instancetype)makeWithTestList:(nullable NSArray *)testList; -@property(nonatomic, strong, nullable) NSArray *testList; +@property(nonatomic, copy, nullable) NSArray *testList; @end /// The codec used by HostIntegrationCoreApi. @@ -144,16 +144,15 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// Returns passed in int. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoInt:(NSNumber *)anInt error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoInt:(NSInteger)anInt error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in double. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoDouble:(NSNumber *)aDouble - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoDouble:(double)aDouble error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed in boolean. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoBool:(NSNumber *)aBool error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoBool:(BOOL)aBool error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed in string. /// /// @return `nil` only when `error != nil`. @@ -241,13 +240,13 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// test basic asynchronous calling. - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion; /// Returns passed in int asynchronously. -- (void)echoAsyncInt:(NSNumber *)anInt +- (void)echoAsyncInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns passed in double asynchronously. -- (void)echoAsyncDouble:(NSNumber *)aDouble +- (void)echoAsyncDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed in boolean asynchronously. -- (void)echoAsyncBool:(NSNumber *)aBool +- (void)echoAsyncBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed string asynchronously. - (void)echoAsyncString:(NSString *)aString @@ -326,11 +325,11 @@ NSObject *HostIntegrationCoreApiGetCodec(void); aString:(nullable NSString *)aNullableString completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoBool:(NSNumber *)aBool +- (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoInt:(NSNumber *)anInt +- (void)callFlutterEchoInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoDouble:(NSNumber *)aDouble +- (void)callFlutterEchoDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @@ -403,13 +402,13 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed boolean, to test serialization and deserialization. -- (void)echoBool:(NSNumber *)aBool +- (void)echoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed int, to test serialization and deserialization. -- (void)echoInt:(NSNumber *)anInt +- (void)echoInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed double, to test serialization and deserialization. -- (void)echoDouble:(NSNumber *)aDouble +- (void)echoDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed string, to test serialization and deserialization. - (void)echoString:(NSString *)aString 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 60bcbb346519..bc3d0433db05 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 @@ -65,10 +65,10 @@ - (NSArray *)toList; @end @implementation AllTypes -+ (instancetype)makeWithABool:(NSNumber *)aBool - anInt:(NSNumber *)anInt - anInt64:(NSNumber *)anInt64 - aDouble:(NSNumber *)aDouble ++ (instancetype)makeWithABool:(BOOL)aBool + anInt:(NSInteger)anInt + anInt64:(NSInteger)anInt64 + aDouble:(double)aDouble aByteArray:(FlutterStandardTypedData *)aByteArray a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray @@ -96,31 +96,19 @@ + (instancetype)makeWithABool:(NSNumber *)aBool } + (AllTypes *)fromList:(NSArray *)list { AllTypes *pigeonResult = [[AllTypes alloc] init]; - pigeonResult.aBool = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.aBool != nil, @""); - pigeonResult.anInt = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.anInt != nil, @""); - pigeonResult.anInt64 = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.anInt64 != nil, @""); - pigeonResult.aDouble = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.aDouble != nil, @""); + pigeonResult.aBool = [GetNullableObjectAtIndex(list, 0) boolValue]; + pigeonResult.anInt = [GetNullableObjectAtIndex(list, 1) integerValue]; + pigeonResult.anInt64 = [GetNullableObjectAtIndex(list, 2) integerValue]; + pigeonResult.aDouble = [GetNullableObjectAtIndex(list, 3) doubleValue]; pigeonResult.aByteArray = GetNullableObjectAtIndex(list, 4); - NSAssert(pigeonResult.aByteArray != nil, @""); pigeonResult.a4ByteArray = GetNullableObjectAtIndex(list, 5); - NSAssert(pigeonResult.a4ByteArray != nil, @""); pigeonResult.a8ByteArray = GetNullableObjectAtIndex(list, 6); - NSAssert(pigeonResult.a8ByteArray != nil, @""); pigeonResult.aFloatArray = GetNullableObjectAtIndex(list, 7); - NSAssert(pigeonResult.aFloatArray != nil, @""); pigeonResult.aList = GetNullableObjectAtIndex(list, 8); - NSAssert(pigeonResult.aList != nil, @""); pigeonResult.aMap = GetNullableObjectAtIndex(list, 9); - NSAssert(pigeonResult.aMap != nil, @""); pigeonResult.anEnum = [GetNullableObjectAtIndex(list, 10) integerValue]; pigeonResult.aString = GetNullableObjectAtIndex(list, 11); - NSAssert(pigeonResult.aString != nil, @""); pigeonResult.anObject = GetNullableObjectAtIndex(list, 12); - NSAssert(pigeonResult.anObject != nil, @""); return pigeonResult; } + (nullable AllTypes *)nullableFromList:(NSArray *)list { @@ -128,19 +116,19 @@ + (nullable AllTypes *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.aBool ?: [NSNull null]), - (self.anInt ?: [NSNull null]), - (self.anInt64 ?: [NSNull null]), - (self.aDouble ?: [NSNull null]), - (self.aByteArray ?: [NSNull null]), - (self.a4ByteArray ?: [NSNull null]), - (self.a8ByteArray ?: [NSNull null]), - (self.aFloatArray ?: [NSNull null]), - (self.aList ?: [NSNull null]), - (self.aMap ?: [NSNull null]), + @(self.aBool), + @(self.anInt), + @(self.anInt64), + @(self.aDouble), + self.aByteArray ?: [NSNull null], + self.a4ByteArray ?: [NSNull null], + self.a8ByteArray ?: [NSNull null], + self.aFloatArray ?: [NSNull null], + self.aList ?: [NSNull null], + self.aMap ?: [NSNull null], @(self.anEnum), - (self.aString ?: [NSNull null]), - (self.anObject ?: [NSNull null]), + self.aString ?: [NSNull null], + self.anObject ?: [NSNull null], ]; } @end @@ -212,23 +200,23 @@ + (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { } - (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.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.aNullableString ?: [NSNull null], + self.aNullableObject ?: [NSNull null], ]; } @end @@ -245,7 +233,6 @@ + (AllClassesWrapper *)fromList:(NSArray *)list { AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - NSAssert(pigeonResult.allNullableTypes != nil, @""); pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))]; return pigeonResult; } @@ -276,7 +263,7 @@ + (nullable TestMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.testList ?: [NSNull null]), + self.testList ?: [NSNull null], ]; } @end @@ -465,7 +452,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoInt:arg_anInt error:&error]; callback(wrapResult(output, error)); @@ -487,7 +474,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoDouble:arg_aDouble error:&error]; callback(wrapResult(output, error)); @@ -509,7 +496,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; NSNumber *output = [api echoBool:arg_aBool error:&error]; callback(wrapResult(output, error)); @@ -1024,7 +1011,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api echoAsyncInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1048,7 +1035,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api echoAsyncDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1072,7 +1059,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api echoAsyncBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1718,7 +1705,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api callFlutterEchoBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1741,7 +1728,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api callFlutterEchoInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1764,7 +1751,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api callFlutterEchoDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2327,14 +2314,14 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool } }]; } -- (void)echoBool:(NSNumber *)arg_aBool +- (void)echoBool:(BOOL)arg_aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoBool" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aBool ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_aBool) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -2353,14 +2340,14 @@ - (void)echoBool:(NSNumber *)arg_aBool } }]; } -- (void)echoInt:(NSNumber *)arg_anInt +- (void)echoInt:(NSInteger)arg_anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoInt" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_anInt ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_anInt) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -2379,14 +2366,14 @@ - (void)echoInt:(NSNumber *)arg_anInt } }]; } -- (void)echoDouble:(NSNumber *)arg_aDouble +- (void)echoDouble:(double)arg_aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoDouble" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aDouble ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_aDouble) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { 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 a4fe6b86d9fd..6c37758ccae8 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 @@ -50,17 +50,16 @@ - (nullable id)throwFlutterErrorWithError:(FlutterError *_Nullable *_Nonnull)err return nil; } -- (nullable NSNumber *)echoInt:(NSNumber *)anInt error:(FlutterError *_Nullable *_Nonnull)error { - return anInt; +- (nullable NSNumber *)echoInt:(NSInteger)anInt error:(FlutterError *_Nullable *_Nonnull)error { + return @(anInt); } -- (nullable NSNumber *)echoDouble:(NSNumber *)aDouble - error:(FlutterError *_Nullable *_Nonnull)error { - return aDouble; +- (nullable NSNumber *)echoDouble:(double)aDouble error:(FlutterError *_Nullable *_Nonnull)error { + return @(aDouble); } -- (nullable NSNumber *)echoBool:(NSNumber *)aBool error:(FlutterError *_Nullable *_Nonnull)error { - return aBool; +- (nullable NSNumber *)echoBool:(BOOL)aBool error:(FlutterError *_Nullable *_Nonnull)error { + return @(aBool); } - (nullable NSString *)echoString:(NSString *)aString @@ -196,19 +195,19 @@ - (void)echoAsyncNullableAllNullableTypes:(nullable AllNullableTypes *)everythin completion(everything, nil); } -- (void)echoAsyncInt:(NSNumber *)anInt +- (void)echoAsyncInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - completion(anInt, nil); + completion(@(anInt), nil); } -- (void)echoAsyncDouble:(NSNumber *)aDouble +- (void)echoAsyncDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - completion(aDouble, nil); + completion(@(aDouble), nil); } -- (void)echoAsyncBool:(NSNumber *)aBool +- (void)echoAsyncBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - completion(aBool, nil); + completion(@(aBool), nil); } - (void)echoAsyncString:(NSString *)aString @@ -331,7 +330,7 @@ - (void)callFlutterSendMultipleNullableTypesABool:(nullable NSNumber *)aNullable }]; } -- (void)callFlutterEchoBool:(NSNumber *)aBool +- (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoBool:aBool completion:^(NSNumber *value, FlutterError *error) { @@ -339,7 +338,7 @@ - (void)callFlutterEchoBool:(NSNumber *)aBool }]; } -- (void)callFlutterEchoInt:(NSNumber *)anInt +- (void)callFlutterEchoInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoInt:anInt completion:^(NSNumber *value, FlutterError *error) { @@ -347,7 +346,7 @@ - (void)callFlutterEchoInt:(NSNumber *)anInt }]; } -- (void)callFlutterEchoDouble:(NSNumber *)aDouble +- (void)callFlutterEchoDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { [self.flutterAPI echoDouble:aDouble completion:^(NSNumber *value, FlutterError *error) { 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 f330fc4c68fd..36ead686fc2b 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 @@ -35,10 +35,10 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @interface AllTypes : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithABool:(NSNumber *)aBool - anInt:(NSNumber *)anInt - anInt64:(NSNumber *)anInt64 - aDouble:(NSNumber *)aDouble ++ (instancetype)makeWithABool:(BOOL)aBool + anInt:(NSInteger)anInt + anInt64:(NSInteger)anInt64 + aDouble:(double)aDouble aByteArray:(FlutterStandardTypedData *)aByteArray a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray @@ -48,16 +48,16 @@ typedef NS_ENUM(NSUInteger, AnEnum) { anEnum:(AnEnum)anEnum aString:(NSString *)aString anObject:(id)anObject; -@property(nonatomic, strong) NSNumber *aBool; -@property(nonatomic, strong) NSNumber *anInt; -@property(nonatomic, strong) NSNumber *anInt64; -@property(nonatomic, strong) NSNumber *aDouble; +@property(nonatomic, assign) BOOL aBool; +@property(nonatomic, assign) NSInteger anInt; +@property(nonatomic, assign) NSInteger anInt64; +@property(nonatomic, assign) double aDouble; @property(nonatomic, strong) FlutterStandardTypedData *aByteArray; @property(nonatomic, strong) FlutterStandardTypedData *a4ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *a8ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *aFloatArray; -@property(nonatomic, strong) NSArray *aList; -@property(nonatomic, strong) NSDictionary *aMap; +@property(nonatomic, copy) NSArray *aList; +@property(nonatomic, copy) NSDictionary *aMap; @property(nonatomic, assign) AnEnum anEnum; @property(nonatomic, copy) NSString *aString; @property(nonatomic, strong) id anObject; @@ -90,12 +90,12 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable4ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable8ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableFloatArray; -@property(nonatomic, strong, nullable) NSArray *aNullableList; -@property(nonatomic, strong, nullable) NSDictionary *aNullableMap; -@property(nonatomic, strong, nullable) NSArray *> *nullableNestedList; -@property(nonatomic, strong, nullable) +@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, strong, nullable) NSDictionary *nullableMapWithObject; +@property(nonatomic, copy, nullable) NSDictionary *nullableMapWithObject; @property(nonatomic, strong, nullable) AnEnumBox *aNullableEnum; @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; @@ -118,7 +118,7 @@ typedef NS_ENUM(NSUInteger, AnEnum) { /// A data class containing a List, used in unit tests. @interface TestMessage : NSObject + (instancetype)makeWithTestList:(nullable NSArray *)testList; -@property(nonatomic, strong, nullable) NSArray *testList; +@property(nonatomic, copy, nullable) NSArray *testList; @end /// The codec used by HostIntegrationCoreApi. @@ -144,16 +144,15 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// Returns passed in int. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoInt:(NSNumber *)anInt error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoInt:(NSInteger)anInt error:(FlutterError *_Nullable *_Nonnull)error; /// Returns passed in double. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoDouble:(NSNumber *)aDouble - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoDouble:(double)aDouble error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed in boolean. /// /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)echoBool:(NSNumber *)aBool error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)echoBool:(BOOL)aBool error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed in string. /// /// @return `nil` only when `error != nil`. @@ -241,13 +240,13 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// test basic asynchronous calling. - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion; /// Returns passed in int asynchronously. -- (void)echoAsyncInt:(NSNumber *)anInt +- (void)echoAsyncInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns passed in double asynchronously. -- (void)echoAsyncDouble:(NSNumber *)aDouble +- (void)echoAsyncDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed in boolean asynchronously. -- (void)echoAsyncBool:(NSNumber *)aBool +- (void)echoAsyncBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed string asynchronously. - (void)echoAsyncString:(NSString *)aString @@ -326,11 +325,11 @@ NSObject *HostIntegrationCoreApiGetCodec(void); aString:(nullable NSString *)aNullableString completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoBool:(NSNumber *)aBool +- (void)callFlutterEchoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoInt:(NSNumber *)anInt +- (void)callFlutterEchoInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoDouble:(NSNumber *)aDouble +- (void)callFlutterEchoDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @@ -403,13 +402,13 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(AllNullableTypes *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed boolean, to test serialization and deserialization. -- (void)echoBool:(NSNumber *)aBool +- (void)echoBool:(BOOL)aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed int, to test serialization and deserialization. -- (void)echoInt:(NSNumber *)anInt +- (void)echoInt:(NSInteger)anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed double, to test serialization and deserialization. -- (void)echoDouble:(NSNumber *)aDouble +- (void)echoDouble:(double)aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed string, to test serialization and deserialization. - (void)echoString:(NSString *)aString 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 60bcbb346519..bc3d0433db05 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 @@ -65,10 +65,10 @@ - (NSArray *)toList; @end @implementation AllTypes -+ (instancetype)makeWithABool:(NSNumber *)aBool - anInt:(NSNumber *)anInt - anInt64:(NSNumber *)anInt64 - aDouble:(NSNumber *)aDouble ++ (instancetype)makeWithABool:(BOOL)aBool + anInt:(NSInteger)anInt + anInt64:(NSInteger)anInt64 + aDouble:(double)aDouble aByteArray:(FlutterStandardTypedData *)aByteArray a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray @@ -96,31 +96,19 @@ + (instancetype)makeWithABool:(NSNumber *)aBool } + (AllTypes *)fromList:(NSArray *)list { AllTypes *pigeonResult = [[AllTypes alloc] init]; - pigeonResult.aBool = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.aBool != nil, @""); - pigeonResult.anInt = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.anInt != nil, @""); - pigeonResult.anInt64 = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.anInt64 != nil, @""); - pigeonResult.aDouble = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.aDouble != nil, @""); + pigeonResult.aBool = [GetNullableObjectAtIndex(list, 0) boolValue]; + pigeonResult.anInt = [GetNullableObjectAtIndex(list, 1) integerValue]; + pigeonResult.anInt64 = [GetNullableObjectAtIndex(list, 2) integerValue]; + pigeonResult.aDouble = [GetNullableObjectAtIndex(list, 3) doubleValue]; pigeonResult.aByteArray = GetNullableObjectAtIndex(list, 4); - NSAssert(pigeonResult.aByteArray != nil, @""); pigeonResult.a4ByteArray = GetNullableObjectAtIndex(list, 5); - NSAssert(pigeonResult.a4ByteArray != nil, @""); pigeonResult.a8ByteArray = GetNullableObjectAtIndex(list, 6); - NSAssert(pigeonResult.a8ByteArray != nil, @""); pigeonResult.aFloatArray = GetNullableObjectAtIndex(list, 7); - NSAssert(pigeonResult.aFloatArray != nil, @""); pigeonResult.aList = GetNullableObjectAtIndex(list, 8); - NSAssert(pigeonResult.aList != nil, @""); pigeonResult.aMap = GetNullableObjectAtIndex(list, 9); - NSAssert(pigeonResult.aMap != nil, @""); pigeonResult.anEnum = [GetNullableObjectAtIndex(list, 10) integerValue]; pigeonResult.aString = GetNullableObjectAtIndex(list, 11); - NSAssert(pigeonResult.aString != nil, @""); pigeonResult.anObject = GetNullableObjectAtIndex(list, 12); - NSAssert(pigeonResult.anObject != nil, @""); return pigeonResult; } + (nullable AllTypes *)nullableFromList:(NSArray *)list { @@ -128,19 +116,19 @@ + (nullable AllTypes *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.aBool ?: [NSNull null]), - (self.anInt ?: [NSNull null]), - (self.anInt64 ?: [NSNull null]), - (self.aDouble ?: [NSNull null]), - (self.aByteArray ?: [NSNull null]), - (self.a4ByteArray ?: [NSNull null]), - (self.a8ByteArray ?: [NSNull null]), - (self.aFloatArray ?: [NSNull null]), - (self.aList ?: [NSNull null]), - (self.aMap ?: [NSNull null]), + @(self.aBool), + @(self.anInt), + @(self.anInt64), + @(self.aDouble), + self.aByteArray ?: [NSNull null], + self.a4ByteArray ?: [NSNull null], + self.a8ByteArray ?: [NSNull null], + self.aFloatArray ?: [NSNull null], + self.aList ?: [NSNull null], + self.aMap ?: [NSNull null], @(self.anEnum), - (self.aString ?: [NSNull null]), - (self.anObject ?: [NSNull null]), + self.aString ?: [NSNull null], + self.anObject ?: [NSNull null], ]; } @end @@ -212,23 +200,23 @@ + (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { } - (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.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.aNullableString ?: [NSNull null], + self.aNullableObject ?: [NSNull null], ]; } @end @@ -245,7 +233,6 @@ + (AllClassesWrapper *)fromList:(NSArray *)list { AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; pigeonResult.allNullableTypes = [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - NSAssert(pigeonResult.allNullableTypes != nil, @""); pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))]; return pigeonResult; } @@ -276,7 +263,7 @@ + (nullable TestMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.testList ?: [NSNull null]), + self.testList ?: [NSNull null], ]; } @end @@ -465,7 +452,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoInt:arg_anInt error:&error]; callback(wrapResult(output, error)); @@ -487,7 +474,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoDouble:arg_aDouble error:&error]; callback(wrapResult(output, error)); @@ -509,7 +496,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; NSNumber *output = [api echoBool:arg_aBool error:&error]; callback(wrapResult(output, error)); @@ -1024,7 +1011,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api echoAsyncInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1048,7 +1035,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api echoAsyncDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1072,7 +1059,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api echoAsyncBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1718,7 +1705,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); + BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api callFlutterEchoBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1741,7 +1728,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); + NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api callFlutterEchoInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -1764,7 +1751,7 @@ void SetUpHostIntegrationCoreApi(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); + double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api callFlutterEchoDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2327,14 +2314,14 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool } }]; } -- (void)echoBool:(NSNumber *)arg_aBool +- (void)echoBool:(BOOL)arg_aBool completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoBool" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aBool ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_aBool) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -2353,14 +2340,14 @@ - (void)echoBool:(NSNumber *)arg_aBool } }]; } -- (void)echoInt:(NSNumber *)arg_anInt +- (void)echoInt:(NSInteger)arg_anInt completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoInt" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_anInt ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_anInt) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -2379,14 +2366,14 @@ - (void)echoInt:(NSNumber *)arg_anInt } }]; } -- (void)echoDouble:(NSNumber *)arg_aDouble +- (void)echoDouble:(double)arg_aDouble completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoDouble" binaryMessenger:self.binaryMessenger codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aDouble ?: [NSNull null] ] + [channel sendMessage:@[ @(arg_aDouble) ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { diff --git a/packages/pigeon/platform_tests/test_plugin/android/build.gradle b/packages/pigeon/platform_tests/test_plugin/android/build.gradle index 1ed406be1f3b..263a4fec6302 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/build.gradle +++ b/packages/pigeon/platform_tests/test_plugin/android/build.gradle @@ -2,7 +2,7 @@ group 'com.example.test_plugin' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.10' + ext.kotlin_version = '1.9.20' repositories { google() mavenCentral() diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/project.pbxproj index 706685fdedd2..60de312721df 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/project.pbxproj @@ -259,7 +259,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1400; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33A341DA291ED36900D34E0F = { diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a33d94b14561..3e0488488eaf 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =2.19.0 <4.0.0" diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index aa506eb714f1..d55bd657498e 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -1194,7 +1194,7 @@ void main() { expect( code, contains( - '@property(nonatomic, strong, nullable) NSDictionary *')); + '@property(nonatomic, copy, nullable) NSDictionary *')); }); test('gen map argument with object', () { @@ -1978,7 +1978,7 @@ void main() { expect( code, contains( - '- (nullable NSNumber *)addX:(NSNumber *)x y:(NSNumber *)y error:(FlutterError *_Nullable *_Nonnull)error;')); + '- (nullable NSNumber *)addX:(NSInteger)x y:(NSInteger)y error:(FlutterError *_Nullable *_Nonnull)error;')); } { final StringBuffer sink = StringBuffer(); @@ -1997,10 +1997,14 @@ void main() { ); final String code = sink.toString(); expect(code, contains('NSArray *args = message;')); - expect(code, - contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);')); - expect(code, - contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);')); + expect( + code, + contains( + 'NSInteger arg_x = [GetNullableObjectAtIndex(args, 0) integerValue];')); + expect( + code, + contains( + 'NSInteger arg_y = [GetNullableObjectAtIndex(args, 1) integerValue];')); expect(code, contains('NSNumber *output = [api addX:arg_x y:arg_y error:&error]')); } @@ -2045,7 +2049,7 @@ void main() { expect( code, contains( - '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;')); + '- (void)addX:(NSInteger)x y:(NSInteger)y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;')); } { final StringBuffer sink = StringBuffer(); @@ -2064,10 +2068,14 @@ void main() { ); final String code = sink.toString(); expect(code, contains('NSArray *args = message;')); - expect(code, - contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);')); - expect(code, - contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);')); + expect( + code, + contains( + 'NSInteger arg_x = [GetNullableObjectAtIndex(args, 0) integerValue];')); + expect( + code, + contains( + 'NSInteger arg_y = [GetNullableObjectAtIndex(args, 1) integerValue];')); expect(code, contains('[api addX:arg_x y:arg_y completion:')); } }); @@ -2110,7 +2118,7 @@ void main() { expect( code, contains( - '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;')); + '- (void)addX:(NSInteger)x y:(NSInteger)y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;')); } { final StringBuffer sink = StringBuffer(); @@ -2131,11 +2139,9 @@ void main() { expect( code, contains( - '- (void)addX:(NSNumber *)arg_x y:(NSNumber *)arg_y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion {')); + '- (void)addX:(NSInteger)arg_x y:(NSInteger)arg_y completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion {')); expect( - code, - contains( - '[channel sendMessage:@[arg_x ?: [NSNull null], arg_y ?: [NSNull null]] reply:')); + code, contains('[channel sendMessage:@[@(arg_x), @(arg_y)] reply:')); } }); diff --git a/packages/pointer_interceptor/.gitignore b/packages/pointer_interceptor/pointer_interceptor/.gitignore similarity index 100% rename from packages/pointer_interceptor/.gitignore rename to packages/pointer_interceptor/pointer_interceptor/.gitignore diff --git a/packages/pointer_interceptor/.metadata b/packages/pointer_interceptor/pointer_interceptor/.metadata similarity index 100% rename from packages/pointer_interceptor/.metadata rename to packages/pointer_interceptor/pointer_interceptor/.metadata diff --git a/packages/pointer_interceptor/AUTHORS b/packages/pointer_interceptor/pointer_interceptor/AUTHORS similarity index 100% rename from packages/pointer_interceptor/AUTHORS rename to packages/pointer_interceptor/pointer_interceptor/AUTHORS diff --git a/packages/pointer_interceptor/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md similarity index 96% rename from packages/pointer_interceptor/CHANGELOG.md rename to packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md index 3a66b061ff78..b6d9709b6f05 100644 --- a/packages/pointer_interceptor/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.3+7 + +* Updates metadata to point to new source folder + ## 0.9.3+6 * Migrates to `dart:ui_web` APIs. diff --git a/packages/pointer_interceptor/LICENSE b/packages/pointer_interceptor/pointer_interceptor/LICENSE similarity index 100% rename from packages/pointer_interceptor/LICENSE rename to packages/pointer_interceptor/pointer_interceptor/LICENSE diff --git a/packages/pointer_interceptor/README.md b/packages/pointer_interceptor/pointer_interceptor/README.md similarity index 100% rename from packages/pointer_interceptor/README.md rename to packages/pointer_interceptor/pointer_interceptor/README.md diff --git a/packages/pointer_interceptor/doc/img/affected-areas.png b/packages/pointer_interceptor/pointer_interceptor/doc/img/affected-areas.png similarity index 100% rename from packages/pointer_interceptor/doc/img/affected-areas.png rename to packages/pointer_interceptor/pointer_interceptor/doc/img/affected-areas.png diff --git a/packages/pointer_interceptor/doc/img/fixed-areas.png b/packages/pointer_interceptor/pointer_interceptor/doc/img/fixed-areas.png similarity index 100% rename from packages/pointer_interceptor/doc/img/fixed-areas.png rename to packages/pointer_interceptor/pointer_interceptor/doc/img/fixed-areas.png diff --git a/packages/pointer_interceptor/example/.gitignore b/packages/pointer_interceptor/pointer_interceptor/example/.gitignore similarity index 100% rename from packages/pointer_interceptor/example/.gitignore rename to packages/pointer_interceptor/pointer_interceptor/example/.gitignore diff --git a/packages/pointer_interceptor/example/.metadata b/packages/pointer_interceptor/pointer_interceptor/example/.metadata similarity index 100% rename from packages/pointer_interceptor/example/.metadata rename to packages/pointer_interceptor/pointer_interceptor/example/.metadata diff --git a/packages/pointer_interceptor/example/README.md b/packages/pointer_interceptor/pointer_interceptor/example/README.md similarity index 100% rename from packages/pointer_interceptor/example/README.md rename to packages/pointer_interceptor/pointer_interceptor/example/README.md diff --git a/packages/pointer_interceptor/example/integration_test/widget_test.dart b/packages/pointer_interceptor/pointer_interceptor/example/integration_test/widget_test.dart similarity index 100% rename from packages/pointer_interceptor/example/integration_test/widget_test.dart rename to packages/pointer_interceptor/pointer_interceptor/example/integration_test/widget_test.dart diff --git a/packages/pointer_interceptor/example/lib/main.dart b/packages/pointer_interceptor/pointer_interceptor/example/lib/main.dart similarity index 100% rename from packages/pointer_interceptor/example/lib/main.dart rename to packages/pointer_interceptor/pointer_interceptor/example/lib/main.dart diff --git a/packages/pointer_interceptor/example/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml similarity index 100% rename from packages/pointer_interceptor/example/pubspec.yaml rename to packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml diff --git a/packages/pointer_interceptor/example/test_driver/integration_test.dart b/packages/pointer_interceptor/pointer_interceptor/example/test_driver/integration_test.dart similarity index 100% rename from packages/pointer_interceptor/example/test_driver/integration_test.dart rename to packages/pointer_interceptor/pointer_interceptor/example/test_driver/integration_test.dart diff --git a/packages/pointer_interceptor/example/web/favicon.png b/packages/pointer_interceptor/pointer_interceptor/example/web/favicon.png similarity index 100% rename from packages/pointer_interceptor/example/web/favicon.png rename to packages/pointer_interceptor/pointer_interceptor/example/web/favicon.png diff --git a/packages/pointer_interceptor/example/web/icons/Icon-192.png b/packages/pointer_interceptor/pointer_interceptor/example/web/icons/Icon-192.png similarity index 100% rename from packages/pointer_interceptor/example/web/icons/Icon-192.png rename to packages/pointer_interceptor/pointer_interceptor/example/web/icons/Icon-192.png diff --git a/packages/pointer_interceptor/example/web/icons/Icon-512.png b/packages/pointer_interceptor/pointer_interceptor/example/web/icons/Icon-512.png similarity index 100% rename from packages/pointer_interceptor/example/web/icons/Icon-512.png rename to packages/pointer_interceptor/pointer_interceptor/example/web/icons/Icon-512.png diff --git a/packages/pointer_interceptor/example/web/index.html b/packages/pointer_interceptor/pointer_interceptor/example/web/index.html similarity index 100% rename from packages/pointer_interceptor/example/web/index.html rename to packages/pointer_interceptor/pointer_interceptor/example/web/index.html diff --git a/packages/pointer_interceptor/example/web/manifest.json b/packages/pointer_interceptor/pointer_interceptor/example/web/manifest.json similarity index 100% rename from packages/pointer_interceptor/example/web/manifest.json rename to packages/pointer_interceptor/pointer_interceptor/example/web/manifest.json diff --git a/packages/pointer_interceptor/lib/pointer_interceptor.dart b/packages/pointer_interceptor/pointer_interceptor/lib/pointer_interceptor.dart similarity index 100% rename from packages/pointer_interceptor/lib/pointer_interceptor.dart rename to packages/pointer_interceptor/pointer_interceptor/lib/pointer_interceptor.dart diff --git a/packages/pointer_interceptor/lib/src/mobile.dart b/packages/pointer_interceptor/pointer_interceptor/lib/src/mobile.dart similarity index 100% rename from packages/pointer_interceptor/lib/src/mobile.dart rename to packages/pointer_interceptor/pointer_interceptor/lib/src/mobile.dart diff --git a/packages/pointer_interceptor/lib/src/web.dart b/packages/pointer_interceptor/pointer_interceptor/lib/src/web.dart similarity index 100% rename from packages/pointer_interceptor/lib/src/web.dart rename to packages/pointer_interceptor/pointer_interceptor/lib/src/web.dart diff --git a/packages/pointer_interceptor/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml similarity index 89% rename from packages/pointer_interceptor/pubspec.yaml rename to packages/pointer_interceptor/pointer_interceptor/pubspec.yaml index 3dba03b703bd..63bfccb53922 100644 --- a/packages/pointer_interceptor/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml @@ -1,8 +1,8 @@ name: pointer_interceptor description: A widget to prevent clicks from being swallowed by underlying HtmlElementViews on the web. -repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor +repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor/pointer_interceptor issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pointer_interceptor%22 -version: 0.9.3+6 +version: 0.9.3+7 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/pointer_interceptor/test/README.md b/packages/pointer_interceptor/pointer_interceptor/test/README.md similarity index 100% rename from packages/pointer_interceptor/test/README.md rename to packages/pointer_interceptor/pointer_interceptor/test/README.md diff --git a/packages/pointer_interceptor/test/tests_exist_elsewhere_test.dart b/packages/pointer_interceptor/pointer_interceptor/test/tests_exist_elsewhere_test.dart similarity index 100% rename from packages/pointer_interceptor/test/tests_exist_elsewhere_test.dart rename to packages/pointer_interceptor/pointer_interceptor/test/tests_exist_elsewhere_test.dart diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 570519e6898e..b61ddbd53a56 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,3 +1,12 @@ +## NEXT + +* Fixes bug where having one reversed axis caused incorrect painting of a pinned row. +* Adds support for BorderRadius in TableSpanDecorations. + +## 0.0.4 + +* Adds TableSpanPadding, TableSpan.padding, and TableSpanDecoration.consumeSpanPadding. + ## 0.0.3 * Fixes paint issue when axes are reversed and TableView has pinned rows and columns. 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 b8829f04ccfa..d94bbfe325b5 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -308,6 +308,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { ); } + // TODO(Piinks): Pinned rows/cols do not account for what is visible on the + // screen. Ostensibly, we would not want to have pinned rows/columns that + // extend beyond the viewport, we would never see them as they would never + // scroll into view. So this currently implementation is fairly assuming + // we will never have rows/cols that are outside of the viewport. We should + // maybe add an assertion for this during layout. + // https://github.com/flutter/flutter/issues/136833 int? get _lastPinnedRow => delegate.pinnedRowCount > 0 ? delegate.pinnedRowCount - 1 : null; int? get _lastPinnedColumn => @@ -667,12 +674,17 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { }) { // TODO(Piinks): Assert here or somewhere else merged cells cannot span // pinned and unpinned cells (for merged cell follow-up), https://github.com/flutter/flutter/issues/131224 + _Span colSpan, rowSpan; double yPaintOffset = -offset.dy; for (int row = start.row; row <= end.row; row += 1) { double xPaintOffset = -offset.dx; - final double rowHeight = _rowMetrics[row]!.extent; + rowSpan = _rowMetrics[row]!; + final double rowHeight = rowSpan.extent; + yPaintOffset += rowSpan.configuration.padding.leading; for (int column = start.column; column <= end.column; column += 1) { - final double columnWidth = _columnMetrics[column]!.extent; + colSpan = _columnMetrics[column]!; + final double columnWidth = colSpan.extent; + xPaintOffset += colSpan.configuration.padding.leading; final TableVicinity vicinity = TableVicinity(column: column, row: row); // TODO(Piinks): Add back merged cells, https://github.com/flutter/flutter/issues/131224 @@ -689,9 +701,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { cell.layout(cellConstraints); cellParentData.layoutOffset = Offset(xPaintOffset, yPaintOffset); } - xPaintOffset += columnWidth; + xPaintOffset += columnWidth + + _columnMetrics[column]!.configuration.padding.trailing; } - yPaintOffset += rowHeight; + yPaintOffset += + rowHeight + _rowMetrics[row]!.configuration.padding.trailing; } } @@ -789,7 +803,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { axisDirectionIsReversed(horizontalAxisDirection) ? 0.0 : _pinnedColumnsExtent, - axisDirectionIsReversed(horizontalAxisDirection) + axisDirectionIsReversed(verticalAxisDirection) ? viewportDimension.height - _pinnedRowsExtent : 0.0, viewportDimension.width - _pinnedColumnsExtent, @@ -836,10 +850,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundColumns = LinkedHashMap(); + final TableSpan rowSpan = _rowMetrics[leading.row]!.configuration; for (int column = leading.column; column <= trailing.column; column++) { - final _Span span = _columnMetrics[column]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final TableSpan columnSpan = _columnMetrics[column]!.configuration; + if (columnSpan.backgroundDecoration != null || + columnSpan.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: column, row: leading.row), )!; @@ -847,18 +862,33 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { TableVicinity(column: column, row: trailing.row), )!; - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset, - ); + Rect getColumnRect(bool consumePadding) { + return Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + consumePadding ? columnSpan.padding.leading : 0.0, + rowSpan.padding.leading, + ), + parentDataOf(trailingCell).paintOffset! + + offset + + Offset(trailingCell.size.width, trailingCell.size.height) + + Offset( + consumePadding ? columnSpan.padding.trailing : 0.0, + rowSpan.padding.trailing, + ), + ); + } - if (span.configuration.backgroundDecoration != null) { - backgroundColumns[rect] = span.configuration.backgroundDecoration!; + if (columnSpan.backgroundDecoration != null) { + final Rect rect = getColumnRect( + columnSpan.backgroundDecoration!.consumeSpanPadding); + backgroundColumns[rect] = columnSpan.backgroundDecoration!; } - if (span.configuration.foregroundDecoration != null) { - foregroundColumns[rect] = span.configuration.foregroundDecoration!; + if (columnSpan.foregroundDecoration != null) { + final Rect rect = getColumnRect( + columnSpan.foregroundDecoration!.consumeSpanPadding); + foregroundColumns[rect] = columnSpan.foregroundDecoration!; } } } @@ -869,10 +899,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundRows = LinkedHashMap(); + final TableSpan columnSpan = _columnMetrics[leading.column]!.configuration; for (int row = leading.row; row <= trailing.row; row++) { - final _Span span = _rowMetrics[row]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final TableSpan rowSpan = _rowMetrics[row]!.configuration; + if (rowSpan.backgroundDecoration != null || + rowSpan.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: leading.column, row: row), )!; @@ -880,17 +911,33 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { TableVicinity(column: trailing.column, row: row), )!; - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset, - ); - if (span.configuration.backgroundDecoration != null) { - backgroundRows[rect] = span.configuration.backgroundDecoration!; + Rect getRowRect(bool consumePadding) { + return Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + columnSpan.padding.leading, + consumePadding ? rowSpan.padding.leading : 0.0, + ), + parentDataOf(trailingCell).paintOffset! + + offset + + Offset(trailingCell.size.width, trailingCell.size.height) + + Offset( + columnSpan.padding.leading, + consumePadding ? rowSpan.padding.trailing : 0.0, + ), + ); } - if (span.configuration.foregroundDecoration != null) { - foregroundRows[rect] = span.configuration.foregroundDecoration!; + + if (rowSpan.backgroundDecoration != null) { + final Rect rect = + getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding); + backgroundRows[rect] = rowSpan.backgroundDecoration!; + } + if (rowSpan.foregroundDecoration != null) { + final Rect rect = + getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding); + foregroundRows[rect] = rowSpan.foregroundDecoration!; } } } @@ -1028,7 +1075,12 @@ class _Span bool get isPinned => _isPinned; late bool _isPinned; - double get trailingOffset => leadingOffset + extent; + double get trailingOffset { + return leadingOffset + + extent + + configuration.padding.leading + + configuration.padding.trailing; + } // ---- Span Management ---- diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart index fbfd9155a15a..27d28d1028b4 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart @@ -10,6 +10,42 @@ import 'package:flutter/widgets.dart'; import 'table.dart'; +/// Defines the leading and trailing padding values of a [TableSpan]. +class TableSpanPadding { + /// Creates a padding configuration for a [TableSpan]. + const TableSpanPadding({ + this.leading = 0.0, + this.trailing = 0.0, + }); + + /// Creates padding where both the [leading] and [trailing] are `value`. + const TableSpanPadding.all(double value) + : leading = value, + trailing = value; + + /// The leading amount of pixels to pad a [TableSpan] by. + /// + /// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this + /// offset will be applied above the row. If the vertical [Axis] is reversed, + /// this will be applied below the row. + /// + /// If the [TableSpan] is a column and the horizontal [Axis] is not reversed, + /// this offset will be applied to the left the column. If the horizontal + /// [Axis] is reversed, this will be applied to the right of the column. + final double leading; + + /// The trailing amount of pixels to pad a [TableSpan] by. + /// + /// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this + /// offset will be applied below the row. If the vertical [Axis] is reversed, + /// this will be applied above the row. + /// + /// If the [TableSpan] is a column and the horizontal [Axis] is not reversed, + /// this offset will be applied to the right the column. If the horizontal + /// [Axis] is reversed, this will be applied to the left of the column. + final double trailing; +} + /// Defines the extent, visual appearance, and gesture handling of a row or /// column in a [TableView]. /// @@ -20,13 +56,14 @@ class TableSpan { /// The [extent] argument must be provided. const TableSpan({ required this.extent, + TableSpanPadding? padding, this.recognizerFactories = const {}, this.onEnter, this.onExit, this.cursor = MouseCursor.defer, this.backgroundDecoration, this.foregroundDecoration, - }); + }) : padding = padding ?? const TableSpanPadding(); /// Defines the extent of the span. /// @@ -34,6 +71,11 @@ class TableSpan { /// represents a column, this is the width of the column. final TableSpanExtent extent; + /// Defines the leading and or trailing extent to pad the row or column by. + /// + /// Defaults to no padding. + final TableSpanPadding padding; + /// Factory for creating [GestureRecognizer]s that want to compete for /// gestures within the [extent] of the span. /// @@ -251,14 +293,70 @@ class MinTableSpanExtent extends CombiningTableSpanExtent { /// A decoration for a [TableSpan]. class TableSpanDecoration { /// Creates a [TableSpanDecoration]. - const TableSpanDecoration({this.border, this.color}); + const TableSpanDecoration({ + this.border, + this.color, + this.borderRadius, + this.consumeSpanPadding = true, + }); /// The border drawn around the span. final TableSpanBorder? border; + /// The radius by which the leading and trailing ends of a row or + /// column will be rounded. + /// + /// Applies to the [border] and [color] of the given [TableSpan]. + final BorderRadius? borderRadius; + /// The color to fill the bounds of the span with. final Color? color; + /// Whether or not the decoration should extend to fill the space created by + /// the [TableSpanPadding]. + /// + /// Defaults to true, meaning if a [TableSpan] is a row, the decoration will + /// apply to the full [TableSpanExtent], including the + /// [TableSpanPadding.leading] and [TableSpanPadding.trailing] for the row. + /// This same row decoration will consume any padding from the column spans so + /// as to decorate the row as one continuous span. + /// + /// {@tool snippet} + /// This example illustrates how [consumeSpanPadding] affects + /// [TableSpanDecoration.color]. By default, the color of the decoration + /// consumes the padding, coloring the row fully by including the padding + /// around the row. When [consumeSpanPadding] is false, the padded area of + /// the row is not decorated. + /// + /// ```dart + /// TableView.builder( + /// rowCount: 4, + /// columnCount: 4, + /// columnBuilder: (int index) => TableSpan( + /// extent: const FixedTableSpanExtent(150.0), + /// padding: const TableSpanPadding(trailing: 10), + /// ), + /// rowBuilder: (int index) => TableSpan( + /// extent: const FixedTableSpanExtent(150.0), + /// padding: TableSpanPadding(leading: 10, trailing: 10), + /// backgroundDecoration: TableSpanDecoration( + /// color: index.isOdd ? Colors.blue : Colors.green, + /// // The background color will not be applied to the padded area. + /// consumeSpanPadding: false, + /// ), + /// ), + /// cellBuilder: (_, TableVicinity vicinity) { + /// return Container( + /// height: 150, + /// width: 150, + /// child: const Center(child: FlutterLogo()), + /// ); + /// }, + /// ); + /// ``` + /// {@end-tool} + final bool consumeSpanPadding; + /// Called to draw the decoration around a span. /// /// The provided [TableSpanDecorationPaintDetails] describes the bounds and @@ -273,15 +371,20 @@ class TableSpanDecoration { /// cells. void paint(TableSpanDecorationPaintDetails details) { if (color != null) { - details.canvas.drawRect( - details.rect, - Paint() - ..color = color! - ..isAntiAlias = false, - ); + final Paint paint = Paint() + ..color = color! + ..isAntiAlias = borderRadius != null; + if (borderRadius == null || borderRadius == BorderRadius.zero) { + details.canvas.drawRect(details.rect, paint); + } else { + details.canvas.drawRRect( + borderRadius!.toRRect(details.rect), + paint, + ); + } } if (border != null) { - border!.paint(details); + border!.paint(details, borderRadius); } } } @@ -325,24 +428,33 @@ class TableSpanBorder { /// cell representing the pinned column and separately with another /// [TableSpanDecorationPaintDetails.rect] containing all the other unpinned /// cells. - void paint(TableSpanDecorationPaintDetails details) { + void paint( + TableSpanDecorationPaintDetails details, + BorderRadius? borderRadius, + ) { final AxisDirection axisDirection = details.axisDirection; switch (axisDirectionToAxis(axisDirection)) { case Axis.horizontal: - paintBorder( - details.canvas, - details.rect, + final Border border = Border( top: axisDirection == AxisDirection.right ? leading : trailing, bottom: axisDirection == AxisDirection.right ? trailing : leading, ); - break; - case Axis.vertical: - paintBorder( + border.paint( details.canvas, details.rect, + borderRadius: borderRadius, + ); + break; + case Axis.vertical: + final Border border = Border( left: axisDirection == AxisDirection.down ? leading : trailing, right: axisDirection == AxisDirection.down ? trailing : leading, ); + border.paint( + details.canvas, + details.rect, + borderRadius: borderRadius, + ); break; } } diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index bd30a0106dbc..9a392a8bf4b2 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.0.3 +version: 0.0.4 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/goldens/single-reversed.pinned.painting.png b/packages/two_dimensional_scrollables/test/table_view/goldens/single-reversed.pinned.painting.png new file mode 100644 index 000000000000..a89a27353c14 Binary files /dev/null and b/packages/two_dimensional_scrollables/test/table_view/goldens/single-reversed.pinned.painting.png differ diff --git a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png index baf2cd358cf6..47c77cd4b132 100644 Binary files a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png and b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png differ diff --git a/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart index 04618bad2b6f..fea0d26e9a1d 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart @@ -161,6 +161,7 @@ void main() { rect: rect, axisDirection: AxisDirection.down, ); + final BorderRadius radius = BorderRadius.circular(10.0); decoration.paint(details); expect(canvas.rect, rect); expect(canvas.paint.color, const Color(0xffff0000)); @@ -168,9 +169,13 @@ void main() { final TestTableSpanBorder border = TestTableSpanBorder( leading: const BorderSide(), ); - decoration = TableSpanDecoration(border: border); + decoration = TableSpanDecoration( + border: border, + borderRadius: radius, + ); decoration.paint(details); expect(border.details, details); + expect(border.radius, radius); }); } @@ -194,8 +199,10 @@ class TestCanvas implements Canvas { class TestTableSpanBorder extends TableSpanBorder { TestTableSpanBorder({super.leading}); TableSpanDecorationPaintDetails? details; + BorderRadius? radius; @override - void paint(TableSpanDecorationPaintDetails details) { + void paint(TableSpanDecorationPaintDetails details, BorderRadius? radius) { this.details = details; + this.radius = radius; } } 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 75cdfb0b9e3d..d910b772bce7 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -329,6 +329,127 @@ void main() { expect(parentData.isVisible, isFalse); }); + testWidgets('TableSpanPadding', (WidgetTester tester) async { + final Map childKeys = + {}; + const TableSpan columnSpan = TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding( + leading: 10.0, + trailing: 20.0, + ), + ); + const TableSpan rowSpan = TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding( + leading: 30.0, + trailing: 40.0, + ), + ); + TableView tableView = TableView.builder( + rowCount: 2, + columnCount: 2, + columnBuilder: (_) => columnSpan, + rowBuilder: (_) => rowSpan, + cellBuilder: (_, TableVicinity vicinity) { + childKeys[vicinity] = childKeys[vicinity] ?? UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + }, + ); + TableViewParentData parentDataOf(RenderBox child) { + return child.parentData! as TableViewParentData; + } + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + // first child + TableVicinity vicinity = const TableVicinity(column: 0, row: 0); + TableViewParentData parentData = parentDataOf( + viewport.firstChild!, + ); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 10.0, // Leading 10 pixels before first column + 30.0, // leading 30 pixels before first row + ), + ); + // after first child + vicinity = const TableVicinity(column: 1, row: 0); + + parentData = parentDataOf( + viewport.childAfter(viewport.firstChild!)!, + ); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 240, // 10 leading + 200 first column + 20 trailing + 10 leading + 30.0, // leading 30 pixels before first row + ), + ); + + // last child + vicinity = const TableVicinity(column: 1, row: 1); + parentData = parentDataOf(viewport.lastChild!); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 240.0, // 10 leading + 200 first column + 20 trailing + 10 leading + 300.0, // 30 leading + 200 first row + 40 trailing + 30 leading + ), + ); + + // reverse + tableView = TableView.builder( + rowCount: 2, + columnCount: 2, + verticalDetails: const ScrollableDetails.vertical(reverse: true), + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + columnBuilder: (_) => columnSpan, + rowBuilder: (_) => rowSpan, + cellBuilder: (_, TableVicinity vicinity) { + childKeys[vicinity] = childKeys[vicinity] ?? UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + viewport = getViewport( + tester, + childKeys.values.first, + ); + // first child + vicinity = const TableVicinity(column: 0, row: 0); + parentData = parentDataOf( + viewport.firstChild!, + ); + expect(parentData.vicinity, vicinity); + // layoutOffset is later corrected for reverse in the paintOffset + expect(parentData.paintOffset, const Offset(590.0, 370.0)); + // after first child + vicinity = const TableVicinity(column: 1, row: 0); + + parentData = parentDataOf( + viewport.childAfter(viewport.firstChild!)!, + ); + expect(parentData.vicinity, vicinity); + expect(parentData.paintOffset, const Offset(360.0, 370.0)); + + // last child + vicinity = const TableVicinity(column: 1, row: 1); + parentData = parentDataOf(viewport.lastChild!); + expect(parentData.vicinity, vicinity); + expect(parentData.paintOffset, const Offset(360.0, 100.0)); + }); + testWidgets('TableSpan gesture hit testing', (WidgetTester tester) async { int tapCounter = 0; // Rows @@ -568,6 +689,190 @@ void main() { expect(rowExtent.delegate.viewportExtent, 600.0); }); + testWidgets('First row/column layout based on padding', + (WidgetTester tester) async { + // Huge padding, first span layout + // Column-wise + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(100), + // This padding is so high, only the first column should be laid out. + padding: TableSpanPadding(leading: 2000), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + // All of these children are so offset by the column padding that they are + // outside of the viewport and cache extent, so all but the very + // first column is laid out. This is so that the ability to scroll the + // table through means such as focus traversal are still accessible. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + expect(find.text('Row: 0 Column: 2'), findsNothing); + expect(find.text('Row: 1 Column: 2'), findsNothing); + + // Row-wise + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + // This padding is so high, no children should be laid out. + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(100), + padding: TableSpanPadding(leading: 2000), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + // All of these children are so offset by the row padding that they are + // outside of the viewport and cache extent, so all but the very + // first row is laid out. This is so that the ability to scroll the + // table through means such as focus traversal are still accessible. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + expect(find.text('Row: 2 Column: 0'), findsNothing); + expect(find.text('Row: 2 Column: 1'), findsNothing); + }); + + testWidgets('lazy layout accounts for gradually accrued padding', + (WidgetTester tester) async { + // Check with gradually accrued paddings + // Column-wise + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // No padding here, check all lazily laid out columns in one row. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 0 Column: 2'), findsOneWidget); + expect(find.text('Row: 0 Column: 3'), findsOneWidget); + expect(find.text('Row: 0 Column: 4'), findsOneWidget); + expect(find.text('Row: 0 Column: 5'), findsOneWidget); + expect(find.text('Row: 0 Column: 6'), findsNothing); + + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding(trailing: 200), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Fewer children laid out. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 0 Column: 2'), findsOneWidget); + expect(find.text('Row: 0 Column: 3'), findsNothing); + expect(find.text('Row: 0 Column: 4'), findsNothing); + expect(find.text('Row: 0 Column: 5'), findsNothing); + expect(find.text('Row: 0 Column: 6'), findsNothing); + + // Row-wise + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // No padding here, check all lazily laid out rows in one column. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 2 Column: 0'), findsOneWidget); + expect(find.text('Row: 3 Column: 0'), findsOneWidget); + expect(find.text('Row: 4 Column: 0'), findsOneWidget); + expect(find.text('Row: 5 Column: 0'), findsNothing); + + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding(trailing: 200), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Fewer children laid out. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 2 Column: 0'), findsOneWidget); + expect(find.text('Row: 3 Column: 0'), findsNothing); + expect(find.text('Row: 4 Column: 0'), findsNothing); + expect(find.text('Row: 5 Column: 0'), findsNothing); + + // Check padding with pinned rows and columns + // TODO(Piinks): Pinned rows/columns are not lazily laid out, should check + // for assertions in this case. Will add in https://github.com/flutter/flutter/issues/136833 + }); + testWidgets('regular layout - no pinning', (WidgetTester tester) async { final ScrollController verticalController = ScrollController(); final ScrollController horizontalController = ScrollController(); @@ -950,14 +1255,19 @@ void main() { (WidgetTester tester) async { // TODO(Piinks): Rewrite this to remove golden files from this repo when // mock_canvas is public - https://github.com/flutter/flutter/pull/131631 - // foreground, background, and precedence per mainAxis + // * foreground, background, and precedence per mainAxis + // * Break out a separate test for padding and radius decorations to + // validate paint rect calls TableView tableView = TableView.builder( rowCount: 2, columnCount: 2, columnBuilder: (int index) => TableSpan( extent: const FixedTableSpanExtent(200.0), - foregroundDecoration: const TableSpanDecoration( - border: TableSpanBorder( + padding: index == 0 ? const TableSpanPadding(trailing: 10) : null, + foregroundDecoration: TableSpanDecoration( + consumeSpanPadding: false, + borderRadius: BorderRadius.circular(10.0), + border: const TableSpanBorder( trailing: BorderSide( color: Colors.orange, width: 3, @@ -965,13 +1275,18 @@ void main() { ), ), backgroundDecoration: TableSpanDecoration( + // consumePadding true by default color: index.isEven ? Colors.red : null, + borderRadius: BorderRadius.circular(30.0), ), ), rowBuilder: (int index) => TableSpan( extent: const FixedTableSpanExtent(200.0), - foregroundDecoration: const TableSpanDecoration( - border: TableSpanBorder( + padding: index == 1 ? const TableSpanPadding(leading: 10) : null, + foregroundDecoration: TableSpanDecoration( + // consumePadding true by default + borderRadius: BorderRadius.circular(30.0), + border: const TableSpanBorder( leading: BorderSide( color: Colors.green, width: 3, @@ -980,12 +1295,16 @@ void main() { ), backgroundDecoration: TableSpanDecoration( color: index.isOdd ? Colors.blue : null, + borderRadius: BorderRadius.circular(30.0), + consumeSpanPadding: false, ), ), cellBuilder: (_, TableVicinity vicinity) { - return const SizedBox.square( - dimension: 200, - child: Center(child: FlutterLogo()), + return Container( + height: 200, + width: 200, + color: Colors.grey.withOpacity(0.5), + child: const Center(child: FlutterLogo()), ); }, ); @@ -1052,8 +1371,9 @@ void main() { (WidgetTester tester) async { // TODO(Piinks): Rewrite this to remove golden files from this repo when // mock_canvas is public - https://github.com/flutter/flutter/pull/131631 - // foreground, background, and precedence per mainAxis - final TableView tableView = TableView.builder( + // * foreground, background, and precedence per mainAxis + // Both reversed - Regression test for https://github.com/flutter/flutter/issues/135386 + TableView tableView = TableView.builder( verticalDetails: const ScrollableDetails.vertical(reverse: true), horizontalDetails: const ScrollableDetails.horizontal(reverse: true), rowCount: 2, @@ -1103,6 +1423,57 @@ void main() { matchesGoldenFile('goldens/reversed.pinned.painting.png'), skip: !runGoldens, ); + + // Only one axis reversed - Regression test for https://github.com/flutter/flutter/issues/136897 + tableView = TableView.builder( + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + rowCount: 2, + pinnedRowCount: 1, + columnCount: 2, + pinnedColumnCount: 1, + columnBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide( + color: Colors.orange, + width: 3, + ), + ), + ), + backgroundDecoration: TableSpanDecoration( + color: index.isEven ? Colors.red : null, + ), + ), + rowBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + leading: BorderSide( + color: Colors.green, + width: 3, + ), + ), + ), + backgroundDecoration: TableSpanDecoration( + color: index.isOdd ? Colors.blue : null, + ), + ), + cellBuilder: (_, TableVicinity vicinity) { + return const SizedBox.square( + dimension: 200, + child: Center(child: FlutterLogo()), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + await expectLater( + find.byType(TableView), + matchesGoldenFile('goldens/single-reversed.pinned.painting.png'), + skip: !runGoldens, + ); }); testWidgets('mouse handling', (WidgetTester tester) async { diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index cebc8177291b..db1516640df6 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,25 @@ +## 6.2.1 + +* Fixes incorrect types in `supportsLaunchMode` and + `supportsCloseForLaunchMode`. + +## 6.2.0 + +_Retracted due to incorrect types in new APIs._ + +* Adds `supportsLaunchMode` for checking whether the current platform supports a + given launch mode, to allow clients that will only work with specific modes + to avoid fallback to a different mode. +* Adds `supportsCloseForLaunchMode` to allow checking programatically if a + launched URL will be able to be closed. Previously the documented behvaior was + that it worked only with the `inAppWebView` launch mode, but this is no longer + true on all platforms with the addition of `inAppBrowserView`. +* Updates the documention for `launchUrl` to clarify that clients should not + rely on any specific behavior of the `platformDefault` launch mode. Changes + to the handling of `platformDefault`, such as Android's recent change from + `inAppWebView` to the new `inAppBrowserView`, are not considered breaking. +* Updates minimum supported SDK version to Flutter 3.13. + ## 6.1.14 * Updates documentation to mention support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index afd60143a54b..bb69a52fb91f 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -66,7 +66,9 @@ See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/u Add any URL schemes passed to `canLaunchUrl` as `` entries in your `AndroidManifest.xml`, otherwise it will return false in most cases starting -on Android 11 (API 30) or higher. A `` +on Android 11 (API 30) or higher. Checking for +`supportsLaunchMode(LaunchMode.inAppBrowserView)` also requires +a `` entry to return anything but false. A `` element must be added to your manifest as a child of the root element. Example: @@ -85,6 +87,10 @@ Example: + + + + ``` @@ -210,10 +216,16 @@ if (!await launchUrl(uri)) { If you need to access files outside of your application's sandbox, you will need to have the necessary [entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox). -## Browser vs in-app Handling +## Browser vs in-app handling On some platforms, web URLs can be launched either in an in-app web view, or in the default browser. The default behavior depends on the platform (see [`launchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launchUrl.html) for details), but a specific mode can be used on supported platforms by passing a `LaunchMode`. + +Platforms that do no support a requested `LaunchMode` will +automatically fall back to a supported mode (usually `platformDefault`). If +your application needs to avoid that fallback behavior, however, you can check +if the current platform supports a given mode with `supportsLaunchMode` before +calling `launchUrl`. diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml index fe01f2fba9a8..5cfc75883726 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml @@ -20,6 +20,10 @@ + + + + diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index 57a0ce9ef470..40307a3f715d 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -62,7 +62,13 @@ class _MyHomePageState extends State { } } - Future _launchInWebViewOrVC(Uri url) async { + Future _launchInBrowserView(Uri url) async { + if (!await launchUrl(url, mode: LaunchMode.inAppBrowserView)) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebView(Uri url) async { if (!await launchUrl(url, mode: LaunchMode.inAppWebView)) { throw Exception('Could not launch $url'); } @@ -99,7 +105,7 @@ class _MyHomePageState extends State { } } - Future _launchUniversalLinkIos(Uri url) async { + Future _launchUniversalLinkIOS(Uri url) async { final bool nativeAppLaunchSucceeded = await launchUrl( url, mode: LaunchMode.externalNonBrowserApplication, @@ -107,7 +113,7 @@ class _MyHomePageState extends State { if (!nativeAppLaunchSucceeded) { await launchUrl( url, - mode: LaunchMode.inAppWebView, + mode: LaunchMode.inAppBrowserView, ); } } @@ -173,7 +179,7 @@ class _MyHomePageState extends State { const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewOrVC(toLaunch); + _launched = _launchInBrowserView(toLaunch); }), child: const Text('Launch in app'), ), @@ -198,7 +204,7 @@ class _MyHomePageState extends State { const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchUniversalLinkIos(toLaunch); + _launched = _launchUniversalLinkIOS(toLaunch); }), child: const Text( 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), @@ -206,7 +212,7 @@ class _MyHomePageState extends State { const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewOrVC(toLaunch); + _launched = _launchInWebView(toLaunch); Timer(const Duration(seconds: 5), () { closeInAppWebView(); }); diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index e09df486b417..18cc9c450443 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0" + sdk: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher/lib/src/link.dart b/packages/url_launcher/url_launcher/lib/src/link.dart index cea6845b12ea..b5c47403097f 100644 --- a/packages/url_launcher/url_launcher/lib/src/link.dart +++ b/packages/url_launcher/url_launcher/lib/src/link.dart @@ -126,7 +126,7 @@ class DefaultLinkDelegate extends StatelessWidget { success = await launchUrl( url, mode: _useWebView - ? LaunchMode.inAppWebView + ? LaunchMode.inAppBrowserView : LaunchMode.externalApplication, ); } on PlatformException { diff --git a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart index 970f04dced57..3169e25dfa7a 100644 --- a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart +++ b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart @@ -22,6 +22,8 @@ PreferredLaunchMode convertLaunchMode(LaunchMode mode) { switch (mode) { case LaunchMode.platformDefault: return PreferredLaunchMode.platformDefault; + case LaunchMode.inAppBrowserView: + return PreferredLaunchMode.inAppBrowserView; case LaunchMode.inAppWebView: return PreferredLaunchMode.inAppWebView; case LaunchMode.externalApplication: diff --git a/packages/url_launcher/url_launcher/lib/src/types.dart b/packages/url_launcher/url_launcher/lib/src/types.dart index 359e293ef82e..2bf56e1b5d8b 100644 --- a/packages/url_launcher/url_launcher/lib/src/types.dart +++ b/packages/url_launcher/url_launcher/lib/src/types.dart @@ -14,9 +14,12 @@ enum LaunchMode { /// implementation. platformDefault, - /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, Safari View Controller). + /// Loads the URL in an in-app web view (e.g., Android WebView). inAppWebView, + /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, SFSafariViewController). + inAppBrowserView, + /// Passes the URL to the OS to be handled by another application. externalApplication, diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart index 45193ff17cb3..ca0bcc6109d0 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart @@ -25,7 +25,8 @@ Future launchUrlString( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if (mode == LaunchMode.inAppWebView && + if ((mode == LaunchMode.inAppWebView || + mode == LaunchMode.inAppBrowserView) && !(urlString.startsWith('https:') || urlString.startsWith('http:'))) { throw ArgumentError.value(urlString, 'urlString', 'To use an in-app web view, you must provide an http(s) URL.'); diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart index b3ce6c279f39..23ea836b7406 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart @@ -4,32 +4,24 @@ import 'dart:async'; -import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +// PreferredLaunchMode is hidden to prevent accidentally using it in APIs at +// this layer. If it is ever needed in this file, it should be imported +// separately with a prefix. +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart' + hide PreferredLaunchMode; import '../url_launcher_string.dart'; import 'type_conversion.dart'; /// Passes [url] to the underlying platform for handling. /// -/// [mode] support varies significantly by platform: -/// - [LaunchMode.platformDefault] is supported on all platforms: -/// - On iOS and Android, this treats web URLs as -/// [LaunchMode.inAppWebView], and all other URLs as -/// [LaunchMode.externalApplication]. -/// - On Windows, macOS, and Linux this behaves like -/// [LaunchMode.externalApplication]. -/// - On web, this uses `webOnlyWindowName` for web URLs, and behaves like -/// [LaunchMode.externalApplication] for any other content. -/// - [LaunchMode.inAppWebView] is currently only supported on iOS and -/// Android. If a non-web URL is passed with this mode, an [ArgumentError] -/// will be thrown. -/// - [LaunchMode.externalApplication] is supported on all platforms. -/// On iOS, this should be used in cases where sharing the cookies of the -/// user's browser is important, such as SSO flows, since Safari View -/// Controller does not share the browser's context. -/// - [LaunchMode.externalNonBrowserApplication] is supported on iOS 10+. -/// This setting is used to require universal links to open in a non-browser -/// application. +/// [mode] support varies significantly by platform. Clients can use +/// [supportsLaunchMode] to query for support, but platforms will fall back to +/// other modes if the requested mode is not supported, so checking is not +/// required. The default behavior of [LaunchMode.platformDefault] is up to each +/// platform, and its behavior for a given platform may change over time as new +/// modes are supported, so clients that want a specific mode should request it +/// rather than rely on any currently observed default behavior. /// /// For web, [webOnlyWindowName] specifies a target for the launch. This /// supports the standard special link target names. For example: @@ -45,7 +37,8 @@ Future launchUrl( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if (mode == LaunchMode.inAppWebView && + if ((mode == LaunchMode.inAppWebView || + mode == LaunchMode.inAppBrowserView) && !(url.scheme == 'https' || url.scheme == 'http')) { throw ArgumentError.value(url, 'url', 'To use an in-app web view, you must provide an http(s) URL.'); @@ -81,8 +74,26 @@ Future canLaunchUrl(Uri url) async { /// Closes the current in-app web view, if one was previously opened by /// [launchUrl]. /// -/// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this -/// call will have no effect. +/// This works only if [supportsCloseForLaunchMode] returns true for the mode +/// that was used by [launchUrl]. Future closeInAppWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } + +/// Returns true if [mode] is supported by the current platform implementation. +/// +/// Calling [launchUrl] with an unsupported mode will fall back to a supported +/// mode, so calling this method is only necessary for cases where the caller +/// needs to know which mode will be used. +Future supportsLaunchMode(LaunchMode mode) { + return UrlLauncherPlatform.instance.supportsMode(convertLaunchMode(mode)); +} + +/// Returns true if [closeInAppWebView] is supported for [mode] in the current +/// platform implementation. +/// +/// If this returns false, [closeInAppWebView] will not work when launching +/// URLs with [mode]. +Future supportsCloseForLaunchMode(LaunchMode mode) { + return UrlLauncherPlatform.instance.supportsMode(convertLaunchMode(mode)); +} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 9feae723f2f1..5947671ffea7 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.1.14 +version: 6.2.1 environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0" + sdk: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" flutter: plugin: @@ -28,15 +28,15 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_android: ^6.0.13 - url_launcher_ios: ^6.0.13 + url_launcher_android: ^6.2.0 + url_launcher_ios: ^6.2.0 # Allow either the pure-native or Dart/native hybrid versions of the desktop # implementations, as both are compatible. - url_launcher_linux: ">=2.0.0 <4.0.0" - url_launcher_macos: ">=2.0.0 <4.0.0" - url_launcher_platform_interface: ^2.1.0 - url_launcher_web: ^2.0.0 - url_launcher_windows: ">=2.0.0 <4.0.0" + url_launcher_linux: ^3.1.0 + url_launcher_macos: ^3.1.0 + url_launcher_platform_interface: ^2.2.0 + url_launcher_web: ^2.2.0 + url_launcher_windows: ^3.1.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index 1585420d9b29..052ca2556e39 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -86,7 +86,7 @@ void main() { mock ..setLaunchExpectations( url: 'http://example.com/foobar', - launchMode: PreferredLaunchMode.inAppWebView, + launchMode: PreferredLaunchMode.inAppBrowserView, universalLinksOnly: false, enableJavaScript: true, enableDomStorage: true, diff --git a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart index 05c8b5e4b375..fc0181d4a4ed 100644 --- a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart +++ b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart @@ -107,4 +107,16 @@ class MockUrlLauncher extends Fake Future closeWebView() async { closeWebViewCalled = true; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + launchMode = mode; + return response!; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + launchMode = mode; + return response!; + } } diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart index d71d07fc8fc4..0c5bfdcf2472 100644 --- a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart +++ b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart @@ -248,4 +248,38 @@ void main() { expect(await launchUrl(emailLaunchUrl), isTrue); }); }); + + group('supportsLaunchMode', () { + test('handles returning true', () async { + mock.setResponse(true); + + expect(await supportsLaunchMode(LaunchMode.inAppBrowserView), true); + expect(mock.launchMode, PreferredLaunchMode.inAppBrowserView); + }); + + test('handles returning false', () async { + mock.setResponse(false); + + expect(await supportsLaunchMode(LaunchMode.inAppBrowserView), false); + expect(mock.launchMode, PreferredLaunchMode.inAppBrowserView); + }); + }); + + group('supportsCloseForLaunchMode', () { + test('handles returning true', () async { + mock.setResponse(true); + + expect( + await supportsCloseForLaunchMode(LaunchMode.inAppBrowserView), true); + expect(mock.launchMode, PreferredLaunchMode.inAppBrowserView); + }); + + test('handles returning false', () async { + mock.setResponse(false); + + expect( + await supportsCloseForLaunchMode(LaunchMode.inAppBrowserView), false); + expect(mock.launchMode, PreferredLaunchMode.inAppBrowserView); + }); + }); } diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index ed8a88314da1..cf0da2faf253 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,14 @@ +## 6.2.0 + +* Adds support for `inAppBrowserView` as a separate launch mode option from + `inAppWebView` mode. `inAppBrowserView` is the preferred in-app mode for most uses, + but does not support `closeInAppWebView`. +* Implements `supportsMode` and `supportsCloseForMode`. + +## 6.1.1 + +* Updates annotations lib to 1.7.0. + ## 6.1.0 * Adds support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index dad5ba7507c6..9626cf53f156 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/android/build.gradle @@ -65,7 +65,7 @@ dependencies { // Java language implementation implementation "androidx.core:core:1.10.1" - implementation 'androidx.annotation:annotation:1.6.0' + implementation 'androidx.annotation:annotation:1.7.0' implementation 'androidx.browser:browser:1.5.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java index eab0d87f5b35..f2294f048f82 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java @@ -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 (v10.0.0), do not edit directly. +// Autogenerated from Pigeon (v10.1.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.urllauncher; @@ -190,9 +190,15 @@ public interface UrlLauncherApi { /** Opens the URL externally, returning true if successful. */ @NonNull Boolean launchUrl(@NonNull String url, @NonNull Map headers); - /** Opens the URL in an in-app WebView, returning true if it opens successfully. */ + /** + * Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully. + */ @NonNull - Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options); + Boolean openUrlInApp( + @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options); + + @NonNull + Boolean supportsCustomTabs(); /** Closes the view opened by [openUrlInSafariViewController]. */ void closeWebView(); @@ -205,7 +211,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -228,7 +236,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -252,16 +262,42 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; String urlArg = (String) args.get(0); - WebViewOptions optionsArg = (WebViewOptions) args.get(1); + Boolean allowCustomTabArg = (Boolean) args.get(1); + WebViewOptions optionsArg = (WebViewOptions) args.get(2); + try { + Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); try { - Boolean output = api.openUrlInWebView(urlArg, optionsArg); + Boolean output = api.supportsCustomTabs(); wrapped.add(0, output); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -276,7 +312,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java index 8ee9bffbb587..028338c6981b 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java @@ -16,9 +16,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; import io.flutter.plugins.urllauncher.Messages.UrlLauncherApi; import io.flutter.plugins.urllauncher.Messages.WebViewOptions; +import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -95,14 +97,16 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options) { + public @NonNull Boolean openUrlInApp( + @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options) { ensureActivity(); assert activity != null; Bundle headersBundle = extractBundle(options.getHeaders()); - // Try to launch using Custom Tabs if they have the necessary functionality. - if (!containsRestrictedHeader(options.getHeaders())) { + // Try to launch using Custom Tabs if they have the necessary functionality, unless the caller + // specifically requested a web view. + if (allowCustomTab && !containsRestrictedHeader(options.getHeaders())) { Uri uri = Uri.parse(url); if (openCustomTab(activity, uri, headersBundle)) { return true; @@ -131,6 +135,11 @@ public void closeWebView() { applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE)); } + @Override + public @NonNull Boolean supportsCustomTabs() { + return CustomTabsClient.getPackageName(applicationContext, Collections.emptyList()) != null; + } + private static boolean openCustomTab( @NonNull Context context, @NonNull Uri uri, @NonNull Bundle headersBundle) { CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build(); diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java index b8bb3b4f21ef..3bffbc614f09 100644 --- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java +++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java @@ -130,7 +130,7 @@ public void launch_returnsTrue() { } @Test - public void openWebView_opensUrl_inWebView() { + public void openUrlInApp_opensUrlInWebViewIfNecessary() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -141,8 +141,9 @@ public void openWebView_opensUrl_inWebView() { headers.put("key", "value"); boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(enableDomStorage) @@ -162,15 +163,39 @@ public void openWebView_opensUrl_inWebView() { } @Test - public void openWebView_opensUrl_inCustomTabs() { + public void openWebView_opensUrlInWebViewIfRequested() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); String url = "https://flutter.dev"; boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + false, + new Messages.WebViewOptions.Builder() + .setEnableJavaScript(false) + .setEnableDomStorage(false) + .setHeaders(new HashMap<>()) + .build()); + + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivity(intentCaptor.capture()); + assertTrue(result); + assertEquals(url, intentCaptor.getValue().getExtras().getString(WebViewActivity.URL_EXTRA)); + } + + @Test + public void openWebView_opensUrlInCustomTabs() { + Activity activity = mock(Activity.class); + UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); + api.setActivity(activity); + String url = "https://flutter.dev"; + + boolean result = + api.openUrlInApp( + url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -185,7 +210,7 @@ public void openWebView_opensUrl_inCustomTabs() { } @Test - public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { + public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -195,8 +220,9 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { headers.put(headerKey, "text/plain"); boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -214,7 +240,7 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { } @Test - public void openWebView_fallsbackTo_inWebView() { + public void openWebView_fallsBackToWebViewIfCustomTabFails() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -224,8 +250,9 @@ public void openWebView_fallsbackTo_inWebView() { .startActivity(any(), isNull()); // for custom tabs intent boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -251,8 +278,9 @@ public void openWebView_handlesEnableJavaScript() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(false) @@ -277,8 +305,9 @@ public void openWebView_handlesHeaders() { headers.put(key1, "value"); headers.put(key2, "value2"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -303,8 +332,9 @@ public void openWebView_handlesEnableDomStorage() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(enableDomStorage) @@ -327,8 +357,9 @@ public void openWebView_throwsForNoCurrentActivity() { assertThrows( Messages.FlutterError.class, () -> - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -350,8 +381,9 @@ public void openWebView_returnsFalse() { .startActivity(any()); // for webview intent boolean result = - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) diff --git a/packages/url_launcher/url_launcher_android/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart index df28069ca1de..36c32f2ba2b0 100644 --- a/packages/url_launcher/url_launcher_android/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_android/example/lib/main.dart @@ -39,6 +39,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; bool _hasCallSupport = false; + bool _hasCustomTabSupport = false; Future? _launched; String _phone = ''; @@ -51,73 +52,77 @@ class _MyHomePageState extends State { _hasCallSupport = result; }); }); + // Check for Android Custom Tab support. + launcher + .supportsMode(PreferredLaunchMode.inAppBrowserView) + .then((bool result) { + setState(() { + _hasCustomTabSupport = result; + }); + }); } Future _launchInBrowser(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( + url, + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInCustomTab(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView), )) { throw Exception('Could not launch $url'); } } Future _launchInWebView(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView), )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithCustomHeaders(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {'my_header_key': 'my_header_value'}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + headers: {'my_header_key': 'my_header_value'}, + )), )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithJavaScript(String url) async { - if (!await launcher.launch( + Future _launchInWebViewWithoutJavaScript(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: true, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + enableJavaScript: false, + )), )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithDomStorage(String url) async { - if (!await launcher.launch( + Future _launchInWebViewWithoutDomStorage(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: true, - universalLinksOnly: false, - headers: {}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: false, + )), )) { throw Exception('Could not launch $url'); } @@ -133,22 +138,12 @@ class _MyHomePageState extends State { Future _makePhoneCall(String phoneNumber) async { // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded. - // Just using 'tel:$phoneNumber' would create invalid URLs in some cases, - // such as spaces in the input, which would cause `launch` to fail on some - // platforms. + // Just using 'tel:$phoneNumber' would create invalid URLs in some cases. final Uri launchUri = Uri( scheme: 'tel', path: phoneNumber, ); - await launcher.launch( - launchUri.toString(), - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: true, - headers: {}, - ); + await launcher.launchUrl(launchUri.toString(), const LaunchOptions()); } @override @@ -186,36 +181,45 @@ class _MyHomePageState extends State { padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), + ElevatedButton( + onPressed: _hasCustomTabSupport + ? () => setState(() { + _launched = _launchInBrowser(toLaunch); + }) + : null, + child: const Text('Launch in browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInBrowser(toLaunch); + _launched = _launchInCustomTab(toLaunch); }), - child: const Text('Launch in browser'), + child: const Text('Launch in Android Custom Tab'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebView(toLaunch); }), - child: const Text('Launch in app'), + child: const Text('Launch in web view'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithCustomHeaders(toLaunch); }), - child: const Text('Launch in app (Custom headers)'), + child: const Text('Launch in web view (Custom headers)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithJavaScript(toLaunch); + _launched = _launchInWebViewWithoutJavaScript(toLaunch); }), - child: const Text('Launch in app (JavaScript ON)'), + child: const Text('Launch in web view (JavaScript OFF)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithDomStorage(toLaunch); + _launched = _launchInWebViewWithoutDomStorage(toLaunch); }), - child: const Text('Launch in app (DOM storage ON)'), + child: const Text('Launch in web view (DOM storage OFF)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( @@ -225,7 +229,7 @@ class _MyHomePageState extends State { launcher.closeWebView(); }); }), - child: const Text('Launch in app + close after 5 seconds'), + child: const Text('Launch in web view + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index e8aee1779b0d..d234884c6b10 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -16,7 +16,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: ../ - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart index 9aed8f7f60f0..9d6ce26ed85b 100644 --- a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_android/lib/src/messages.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 (v10.0.0), do not edit directly. +// Autogenerated from Pigeon (v10.1.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 @@ -79,7 +79,8 @@ class UrlLauncherApi { /// Returns true if the URL can definitely be launched. Future canLaunchUrl(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url]) as List?; @@ -108,7 +109,8 @@ class UrlLauncherApi { Future launchUrl( String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url, arg_headers]) as List?; @@ -133,15 +135,44 @@ class UrlLauncherApi { } } - /// Opens the URL in an in-app WebView, returning true if it opens - /// successfully. - Future openUrlInWebView( - String arg_url, WebViewOptions arg_options) async { + /// Opens the URL in an in-app Custom Tab or WebView, returning true if it + /// opens successfully. + Future openUrlInApp(String arg_url, bool arg_allowCustomTab, + WebViewOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp', + codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_url, arg_options]) as List?; + await channel.send([arg_url, arg_allowCustomTab, arg_options]) + 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 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 bool?)!; + } + } + + Future supportsCustomTabs() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -166,7 +197,8 @@ class UrlLauncherApi { /// Closes the view opened by [openUrlInSafariViewController]. Future closeWebView() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.closeWebView', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { diff --git a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart index 7b53b85f7936..f121084b7578 100644 --- a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart +++ b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart @@ -49,8 +49,6 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return _hostApi.closeWebView(); } - // TODO(stuartmorgan): Implement launchUrl, and make this a passthrough - // to launchUrl. See also https://github.com/flutter/flutter/issues/66721 @override Future launch( String url, { @@ -62,16 +60,57 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { required Map headers, String? webOnlyWindowName, }) async { + return launchUrl( + url, + LaunchOptions( + mode: useWebView + ? PreferredLaunchMode.inAppWebView + : PreferredLaunchMode.externalApplication, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: enableDomStorage, + enableJavaScript: enableJavaScript, + headers: headers))); + } + + @override + Future launchUrl(String url, LaunchOptions options) async { + final bool inApp; + switch (options.mode) { + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + inApp = true; + break; + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + // TODO(stuartmorgan): Add full support for + // externalNonBrowsingApplication; see + // https://github.com/flutter/flutter/issues/66721. + // Currently it's treated the same as externalApplication. + inApp = false; + break; + case PreferredLaunchMode.platformDefault: + // Intentionally treat any new values as platformDefault; see comment in + // supportsMode. + // ignore: no_default_cases + default: + // By default, open web URLs in the application. + inApp = url.startsWith('http:') || url.startsWith('https:'); + break; + } + final bool succeeded; - if (useWebView) { - succeeded = await _hostApi.openUrlInWebView( + if (inApp) { + succeeded = await _hostApi.openUrlInApp( url, + // Prefer custom tabs unless a webview was specifically requested. + options.mode != PreferredLaunchMode.inAppWebView, WebViewOptions( - enableJavaScript: enableJavaScript, - enableDomStorage: enableDomStorage, - headers: headers)); + enableJavaScript: options.webViewConfiguration.enableJavaScript, + enableDomStorage: options.webViewConfiguration.enableDomStorage, + headers: options.webViewConfiguration.headers)); } else { - succeeded = await _hostApi.launchUrl(url, headers); + succeeded = + await _hostApi.launchUrl(url, options.webViewConfiguration.headers); } // TODO(stuartmorgan): Remove this special handling as part of a // breaking change to rework failure handling across all platform. The @@ -84,6 +123,29 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return succeeded; } + @override + Future supportsMode(PreferredLaunchMode mode) async { + switch (mode) { + case PreferredLaunchMode.platformDefault: + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.externalApplication: + return true; + case PreferredLaunchMode.inAppBrowserView: + return _hostApi.supportsCustomTabs(); + // Default is a desired behavior here since support for new modes is + // always opt-in, and the enum lives in a different package, so silently + // adding "false" for new values is the correct behavior. + // ignore: no_default_cases + default: + return false; + } + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.inAppWebView; + } + // Returns the part of [url] up to the first ':', or an empty string if there // is no ':'. This deliberately does not use [Uri] to extract the scheme // so that it works on strings that aren't actually valid URLs, since Android diff --git a/packages/url_launcher/url_launcher_android/pigeons/messages.dart b/packages/url_launcher/url_launcher_android/pigeons/messages.dart index 84e507d70248..d71844134092 100644 --- a/packages/url_launcher/url_launcher_android/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_android/pigeons/messages.dart @@ -33,9 +33,11 @@ abstract class UrlLauncherApi { /// Opens the URL externally, returning true if successful. bool launchUrl(String url, Map headers); - /// Opens the URL in an in-app WebView, returning true if it opens - /// successfully. - bool openUrlInWebView(String url, WebViewOptions options); + /// Opens the URL in an in-app Custom Tab or WebView, returning true if it + /// opens successfully. + bool openUrlInApp(String url, bool allowCustomTab, WebViewOptions options); + + bool supportsCustomTabs(); /// Closes the view opened by [openUrlInSafariViewController]. void closeWebView(); diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index f5db21c93b4a..5b5d06e8f238 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.0 +version: 6.2.0 environment: sdk: ">=2.19.0 <4.0.0" flutter: ">=3.7.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart index 3b3d012a1683..2b331cbd027b 100644 --- a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart +++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart @@ -52,7 +52,7 @@ void main() { }); }); - group('launch without webview', () { + group('legacy launch without webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -88,7 +88,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'noactivity://', + 'https://noactivity', useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -116,7 +116,7 @@ void main() { }); }); - group('launch with webview', () { + group('legacy launch with webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -130,6 +130,7 @@ void main() { ); expect(launched, true); expect(api.usedWebView, true); + expect(api.allowedCustomTab, false); expect(api.passedWebViewOptions?.enableDomStorage, false); expect(api.passedWebViewOptions?.enableJavaScript, false); expect(api.passedWebViewOptions?.headers, isEmpty); @@ -169,7 +170,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'noactivity://scheme', + 'https://noactivity', useSafariVC: false, useWebView: true, enableJavaScript: false, @@ -197,12 +198,198 @@ void main() { }); }); - group('closeWebView', () { + group('launch without webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await launcher.closeWebView(); + final bool launched = await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + ); + expect(launched, true); + expect(api.usedWebView, false); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); - expect(api.closed, true); + test('passes headers', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.externalApplication, + webViewConfiguration: InAppWebViewConfiguration( + headers: {'key': 'value'})), + ); + expect(api.passedWebViewOptions?.headers.length, 1); + expect(api.passedWebViewOptions?.headers['key'], 'value'); + }); + + test('passes through no-activity exception', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('https://noactivity', const LaunchOptions()), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('unknown://scheme', const LaunchOptions()), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('launch with webview', () { + test('calls through', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, false); + expect(api.passedWebViewOptions?.enableDomStorage, true); + expect(api.passedWebViewOptions?.enableJavaScript, true); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); + + test('passes enableJavaScript to webview', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: + InAppWebViewConfiguration(enableJavaScript: false))); + + expect(api.passedWebViewOptions?.enableJavaScript, false); + }); + + test('passes enableDomStorage to webview', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: + InAppWebViewConfiguration(enableDomStorage: false))); + + expect(api.passedWebViewOptions?.enableDomStorage, false); + }); + + test('passes through no-activity exception', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('https://noactivity', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('unknown://scheme', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('launch with custom tab', () { + test('calls through', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + }); + + group('launch with platform default', () { + test('uses custom tabs for http', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'http://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + + test('uses custom tabs for https', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'https://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + + test('uses external for other schemes', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'supportedcustomscheme://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, false); + }); + }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns true for in app web view', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); + }); + + test('returns true for in app browser view when available', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + api.hasCustomTabSupport = true; + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + + test('returns false for in app browser view when not available', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + api.hasCustomTabSupport = false; + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + }); + }); + + group('supportsCloseForMode', () { + test('returns true for in app web view', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher.supportsCloseForMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), + false); }); }); } @@ -211,8 +398,10 @@ void main() { /// /// See _launch for the behaviors. class _FakeUrlLauncherApi implements UrlLauncherApi { + bool hasCustomTabSupport = true; WebViewOptions? passedWebViewOptions; bool? usedWebView; + bool? allowedCustomTab; bool? closed; /// A domain that will be treated as having no handler, even for http(s). @@ -237,20 +426,29 @@ class _FakeUrlLauncherApi implements UrlLauncherApi { } @override - Future openUrlInWebView(String url, WebViewOptions options) async { + Future openUrlInApp( + String url, bool allowCustomTab, WebViewOptions options) async { passedWebViewOptions = options; usedWebView = true; + allowedCustomTab = allowCustomTab; return _launch(url); } + @override + Future supportsCustomTabs() async { + return hasCustomTabSupport; + } + bool _launch(String url) { final String scheme = url.split(':')[0]; switch (scheme) { case 'http': case 'https': + case 'supportedcustomscheme': + if (url.endsWith('noactivity')) { + throw PlatformException(code: 'NO_ACTIVITY'); + } return !url.contains(specialHandlerDomain); - case 'noactivity': - throw PlatformException(code: 'NO_ACTIVITY'); default: return false; } diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index ad52522d0021..4e9d077e55d4 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,3 +1,11 @@ +## 6.2.1 + +* Migrates plugin from Objective-C to Swift. + +## 6.2.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 6.1.5 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj index e40cd34be9d7..c10bff136dc4 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -269,7 +269,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -631,6 +631,7 @@ baseConfigurationReference = 666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -651,6 +652,7 @@ baseConfigurationReference = D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ad0ebfab1b88..fa4e0bbd319c 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { + return URL(string: "b a d U R L") == nil +} + final class URLLauncherTests: XCTestCase { - private func createPlugin() -> FLTURLLauncherPlugin { + private func createPlugin() -> URLLauncherPlugin { let launcher = FakeLauncher() - return FLTURLLauncherPlugin(launcher: launcher) + return URLLauncherPlugin(launcher: launcher) } - private func createPlugin(launcher: FakeLauncher) -> FLTURLLauncherPlugin { - FLTURLLauncherPlugin(launcher: launcher) + private func createPlugin(launcher: FakeLauncher) -> URLLauncherPlugin { + return URLLauncherPlugin(launcher: launcher) } func testCanLaunchSuccess() { - var error: FlutterError? - let result = createPlugin().canLaunchURL("good://url", error: &error) - - XCTAssertNotNil(result) - XCTAssertTrue(result?.boolValue ?? false) - XCTAssertNil(error) + let result = createPlugin().canLaunchUrl(url: "good://url") + XCTAssertEqual(result, .success) } func testCanLaunchFailure() { - var error: FlutterError? - let result = createPlugin().canLaunchURL("bad://url", error: &error) - - XCTAssertNotNil(result) - XCTAssertFalse(result?.boolValue ?? true) + let result = createPlugin().canLaunchUrl(url: "bad://url") + XCTAssertEqual(result, .failure) } func testCanLaunchFailureWithInvalidURL() { - var error: FlutterError? - let result = createPlugin().canLaunchURL("urls can't have spaces", error: &error) - - if (error == nil) { - // When linking against the iOS 17 SDK or later, NSURL uses a lenient parser, and won't - // fail to parse URLs, so the test must allow for either outcome. - XCTAssertNotNil(result) - XCTAssertFalse(result?.boolValue ?? true) - XCTAssertNil(error) + let result = createPlugin().canLaunchUrl(url: "urls can't have spaces") + + if urlParsingIsStrict() { + XCTAssertEqual(result, .invalidUrl) } else { - XCTAssertNil(result) - XCTAssertNotNil(error) - XCTAssertEqual(error?.code, "argument_error") - XCTAssertEqual(error?.message, "Unable to parse URL") - XCTAssertEqual(error?.details as? String, "Provided URL: urls can't have spaces") + XCTAssertEqual(result, .failure) } } func testLaunchSuccess() { let expectation = XCTestExpectation(description: "completion called") - createPlugin().launchURL("good://url", universalLinksOnly: false) { result, error in - XCTAssertNotNil(result) - XCTAssertTrue(result?.boolValue ?? false) - XCTAssertNil(error) + createPlugin().launchUrl(url: "good://url", universalLinksOnly: false) { result in + switch result { + case .success(let details): + XCTAssertEqual(details, .success) + case .failure(let error): + XCTFail("Unexpected error: \(error)") + } expectation.fulfill() } @@ -68,11 +61,13 @@ final class URLLauncherTests: XCTestCase { func testLaunchFailure() { let expectation = XCTestExpectation(description: "completion called") - - createPlugin().launchURL("bad://url", universalLinksOnly: false) { result, error in - XCTAssertNotNil(result) - XCTAssertFalse(result?.boolValue ?? true) - XCTAssertNil(error) + createPlugin().launchUrl(url: "bad://url", universalLinksOnly: false) { result in + switch result { + case .success(let details): + XCTAssertEqual(details, .failure) + case .failure(let error): + XCTFail("Unexpected error: \(error)") + } expectation.fulfill() } @@ -81,22 +76,17 @@ final class URLLauncherTests: XCTestCase { func testLaunchFailureWithInvalidURL() { let expectation = XCTestExpectation(description: "completion called") - - createPlugin().launchURL("urls can't have spaces", universalLinksOnly: false) { result, error in - if (error == nil) { - // When linking against the iOS 17 SDK or later, NSURL uses a lenient parser, and won't - // fail to parse URLs, so the test must allow for either outcome. - XCTAssertNotNil(result) - XCTAssertFalse(result?.boolValue ?? true) - XCTAssertNil(error) - } else { - XCTAssertNil(result) - XCTAssertNotNil(error) - XCTAssertEqual(error?.code, "argument_error") - XCTAssertEqual(error?.message, "Unable to parse URL") - XCTAssertEqual(error?.details as? String, "Provided URL: urls can't have spaces") + createPlugin().launchUrl(url: "urls can't have spaces", universalLinksOnly: false) { result in + switch result { + case .success(let details): + if urlParsingIsStrict() { + XCTAssertEqual(details, .invalidUrl) + } else { + XCTAssertEqual(details, .failure) + } + case .failure(let error): + XCTFail("Unexpected error: \(error)") } - expectation.fulfill() } @@ -108,13 +98,17 @@ final class URLLauncherTests: XCTestCase { let plugin = createPlugin(launcher: launcher) let expectation = XCTestExpectation(description: "completion called") - plugin.launchURL("good://url", universalLinksOnly: false) { result, error in - XCTAssertNil(error) + plugin.launchUrl(url: "good://url", universalLinksOnly: false) { result in + switch result { + case .success(let details): + XCTAssertEqual(details, .success) + case .failure(let error): + XCTFail("Unexpected error: \(error)") + } expectation.fulfill() } wait(for: [expectation], timeout: 1) - XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, false) } @@ -123,31 +117,35 @@ final class URLLauncherTests: XCTestCase { let plugin = createPlugin(launcher: launcher) let expectation = XCTestExpectation(description: "completion called") - - plugin.launchURL("good://url", universalLinksOnly: true) { result, error in - XCTAssertNil(error) + plugin.launchUrl(url: "good://url", universalLinksOnly: true) { result in + switch result { + case .success(let details): + XCTAssertEqual(details, .success) + case .failure(let error): + XCTFail("Unexpected error: \(error)") + } expectation.fulfill() } wait(for: [expectation], timeout: 1) - XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, true) } } -final private class FakeLauncher: NSObject, FULLauncher { +final private class FakeLauncher: NSObject, Launcher { var passedOptions: [UIApplication.OpenExternalURLOptionsKey: Any]? - func canOpen(_ url: URL) -> Bool { - return url.scheme == "good" + func canOpenURL(_ url: URL) -> Bool { + url.scheme == "good" } func open( - _ url: URL, options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:], - completionHandler: ((Bool) -> Void)? = nil + _ url: URL, + options: [UIApplication.OpenExternalURLOptionsKey: Any], + completionHandler completion: ((Bool) -> Void)? ) { self.passedOptions = options - completionHandler?(url.scheme == "good") + completion?(url.scheme == "good") } } diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index d755c553f6af..3daaa9acd5ca 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -16,7 +16,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: ../ - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h deleted file mode 100644 index 7b3480e3d47d..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h +++ /dev/null @@ -1,10 +0,0 @@ -// 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 "messages.g.h" - -@interface FLTURLLauncherPlugin : NSObject -@end diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m deleted file mode 100644 index 5d6a75f97aa2..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m +++ /dev/null @@ -1,202 +0,0 @@ -// 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 "FLTURLLauncherPlugin.h" -#import "FLTURLLauncherPlugin_Test.h" -#import "FULLauncher.h" -#import "messages.g.h" - -typedef void (^OpenInSafariVCResponse)(NSNumber *_Nullable, FlutterError *_Nullable); - -@interface FLTURLLaunchSession : NSObject - -@property(copy, nonatomic) OpenInSafariVCResponse completion; -@property(strong, nonatomic) NSURL *url; -@property(strong, nonatomic) SFSafariViewController *safari; -@property(nonatomic, copy) void (^didFinish)(void); - -@end - -@implementation FLTURLLaunchSession - -- (instancetype)initWithURL:url completion:completion { - self = [super init]; - if (self) { - self.url = url; - self.completion = completion; - self.safari = [[SFSafariViewController alloc] initWithURL:url]; - self.safari.delegate = self; - } - return self; -} - -- (void)safariViewController:(SFSafariViewController *)controller - didCompleteInitialLoad:(BOOL)didLoadSuccessfully { - if (didLoadSuccessfully) { - self.completion(@YES, nil); - } else { - self.completion( - nil, [FlutterError - errorWithCode:@"Error" - message:[NSString stringWithFormat:@"Error while launching %@", self.url] - details:nil]); - } -} - -- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { - [controller dismissViewControllerAnimated:YES completion:nil]; - self.didFinish(); -} - -- (void)close { - [self safariViewControllerDidFinish:self.safari]; -} - -@end - -#pragma mark - - -/// Default implementation of FULLancher, using UIApplication. -@interface FULUIApplicationLauncher : NSObject -@end - -@implementation FULUIApplicationLauncher -- (BOOL)canOpenURL:(nonnull NSURL *)url { - return [[UIApplication sharedApplication] canOpenURL:url]; -} - -- (void)openURL:(nonnull NSURL *)url - options:(nonnull NSDictionary *)options - completionHandler:(void (^_Nullable)(BOOL))completion { - [[UIApplication sharedApplication] openURL:url options:options completionHandler:completion]; -} - -@end - -#pragma mark - - -@interface FLTURLLauncherPlugin () - -@property(strong, nonatomic) FLTURLLaunchSession *currentSession; -@property(strong, nonatomic) NSObject *launcher; - -@end - -@implementation FLTURLLauncherPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init]; - FULUrlLauncherApiSetup(registrar.messenger, plugin); -} - -- (instancetype)init { - return [self initWithLauncher:[[FULUIApplicationLauncher alloc] init]]; -} - -- (instancetype)initWithLauncher:(NSObject *)launcher { - if (self = [super init]) { - _launcher = launcher; - } - return self; -} - -- (nullable NSNumber *)canLaunchURL:(NSString *)urlString - error:(FlutterError *_Nullable *_Nonnull)error { - NSURL *url = [NSURL URLWithString:urlString]; - if (!url) { - *error = [self invalidURLErrorForURLString:urlString]; - return nil; - } - return @([self.launcher canOpenURL:url]); -} - -- (void)launchURL:(NSString *)urlString - universalLinksOnly:(NSNumber *)universalLinksOnly - completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - NSURL *url = [NSURL URLWithString:urlString]; - if (!url) { - completion(nil, [self invalidURLErrorForURLString:urlString]); - return; - } - NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly}; - [self.launcher openURL:url - options:options - completionHandler:^(BOOL success) { - completion(@(success), nil); - }]; -} - -- (void)openSafariViewControllerWithURL:(NSString *)urlString - completion:(OpenInSafariVCResponse)completion { - NSURL *url = [NSURL URLWithString:urlString]; - if (!url) { - completion(nil, [self invalidURLErrorForURLString:urlString]); - return; - } - self.currentSession = [[FLTURLLaunchSession alloc] initWithURL:url completion:completion]; - __weak typeof(self) weakSelf = self; - self.currentSession.didFinish = ^(void) { - weakSelf.currentSession = nil; - }; - [self.topViewController presentViewController:self.currentSession.safari - animated:YES - completion:nil]; -} - -- (void)closeSafariViewControllerWithError:(FlutterError *_Nullable *_Nonnull)error { - [self.currentSession close]; -} - -- (UIViewController *)topViewController { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // TODO(stuartmorgan) Provide a non-deprecated codepath. See - // https://github.com/flutter/flutter/issues/104117 - return [self topViewControllerFromViewController:[UIApplication sharedApplication] - .keyWindow.rootViewController]; -#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. - */ -- (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { - if ([viewController isKindOfClass:[UINavigationController class]]) { - UINavigationController *navigationController = (UINavigationController *)viewController; - return [self - topViewControllerFromViewController:[navigationController.viewControllers lastObject]]; - } - if ([viewController isKindOfClass:[UITabBarController class]]) { - UITabBarController *tabController = (UITabBarController *)viewController; - return [self topViewControllerFromViewController:tabController.selectedViewController]; - } - if (viewController.presentedViewController) { - return [self topViewControllerFromViewController:viewController.presentedViewController]; - } - return viewController; -} - -/** - * Creates an error for an invalid URL string. - * - * @param url The invalid URL string - * @return The error to return - */ -- (FlutterError *)invalidURLErrorForURLString:(NSString *)url { - return [FlutterError errorWithCode:@"argument_error" - message:@"Unable to parse URL" - details:[NSString stringWithFormat:@"Provided URL: %@", url]]; -} -@end diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h deleted file mode 100644 index 112682a94693..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h +++ /dev/null @@ -1,11 +0,0 @@ -// 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 "FLTURLLauncherPlugin.h" -#import "FULLauncher.h" - -/// APIs exposed for testing. -@interface FLTURLLauncherPlugin (Test) -- (instancetype)initWithLauncher:(NSObject *)launcher; -@end diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h deleted file mode 100644 index 63f8e04b66da..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h +++ /dev/null @@ -1,19 +0,0 @@ -// 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 - -NS_ASSUME_NONNULL_BEGIN - -/// Protocol for UIApplication methods relating to launching URLs. -/// -/// This protocol exists to allow injecting an alternate implementation for testing. -@protocol FULLauncher -- (BOOL)canOpenURL:(NSURL *)url; -- (void)openURL:(NSURL *)url - options:(NSDictionary *)options - completionHandler:(void (^__nullable)(BOOL success))completion; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift new file mode 100644 index 000000000000..f97db9db9c5e --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift @@ -0,0 +1,20 @@ +// 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. + +/// Protocol for UIApplication methods relating to launching URLs. +/// +/// This protocol exists to allow injecting an alternate implementation for testing. +protocol Launcher { + /// Returns a Boolean value that indicates whether an app is available to handle a URL scheme. + func canOpenURL(_ url: URL) -> Bool + + /// Attempts to asynchronously open the resource at the specified URL. + func open( + _ url: URL, + options: [UIApplication.OpenExternalURLOptionsKey: Any], + completionHandler completion: ((Bool) -> Void)?) +} + +/// Launcher is intentionally a direct passthroguh to UIApplication. +extension UIApplication: Launcher {} diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift new file mode 100644 index 000000000000..b0761e57f08e --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift @@ -0,0 +1,63 @@ +// 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 SafariServices + +typealias OpenInSafariCompletionHandler = (Result) -> Void + +/// A session responsible for launching a URL in Safari and handling its events. +final class URLLaunchSession: NSObject, SFSafariViewControllerDelegate { + + private let completion: OpenInSafariCompletionHandler + private let url: URL + + /// The Safari view controller used for displaying the URL. + let safariViewController: SFSafariViewController + + // A closure to be executed after the Safari view controller finishes. + var didFinish: (() -> Void)? + + /// Initializes a new URLLaunchSession with the provided URL and completion handler. + /// + /// - Parameters: + /// - url: The URL to be opened in Safari. + /// - completion: The completion handler to be called after attempting to open the URL. + init(url: URL, completion: @escaping OpenInSafariCompletionHandler) { + self.url = url + self.completion = completion + self.safariViewController = SFSafariViewController(url: url) + super.init() + self.safariViewController.delegate = self + } + + /// Called when the Safari view controller completes the initial load. + /// + /// - Parameters: + /// - controller: The Safari view controller. + /// - didLoadSuccessfully: Indicates if the initial load was successful. + func safariViewController( + _ controller: SFSafariViewController, + didCompleteInitialLoad didLoadSuccessfully: Bool + ) { + if didLoadSuccessfully { + completion(.success(.success)) + } else { + completion(.success(.failedToLoad)) + } + } + + /// Called when the user finishes using the Safari view controller. + /// + /// - Parameter controller: The Safari view controller. + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + controller.dismiss(animated: true, completion: nil) + didFinish?() + } + + /// Closes the Safari view controller. + func close() { + safariViewControllerDidFinish(safariViewController) + } +} diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift new file mode 100644 index 000000000000..18800319218e --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift @@ -0,0 +1,99 @@ +// 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 + +public final class URLLauncherPlugin: NSObject, FlutterPlugin, UrlLauncherApi { + + public static func register(with registrar: FlutterPluginRegistrar) { + let plugin = URLLauncherPlugin() + UrlLauncherApiSetup.setUp(binaryMessenger: registrar.messenger(), api: plugin) + registrar.publish(plugin) + } + + private var currentSession: URLLaunchSession? + private let launcher: Launcher + + private var topViewController: UIViewController? { + // TODO(stuartmorgan) Provide a non-deprecated codepath. See + // https://github.com/flutter/flutter/issues/104117 + UIApplication.shared.keyWindow?.rootViewController?.topViewController + } + + init(launcher: Launcher = UIApplication.shared) { + self.launcher = launcher + } + + func canLaunchUrl(url: String) -> LaunchResult { + guard let url = URL(string: url) else { + return .invalidUrl + } + let canOpen = launcher.canOpenURL(url) + return canOpen ? .success : .failure + } + + func launchUrl( + url: String, + universalLinksOnly: Bool, + completion: @escaping (Result) -> Void + ) { + guard let url = URL(string: url) else { + completion(.success(.invalidUrl)) + return + } + let options = [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: universalLinksOnly] + launcher.open(url, options: options) { result in + completion(.success(result ? .success : .failure)) + } + } + + func openUrlInSafariViewController( + url: String, + completion: @escaping (Result) -> Void + ) { + guard let url = URL(string: url) else { + completion(.success(.invalidUrl)) + return + } + + let session = URLLaunchSession(url: url, completion: completion) + currentSession = session + + session.didFinish = { [weak self] in + self?.currentSession = nil + } + topViewController?.present(session.safariViewController, animated: true, completion: nil) + } + + func closeSafariViewController() { + currentSession?.close() + } +} + +/// This method recursively iterates 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. +extension UIViewController { + var topViewController: UIViewController { + if let navigationController = self as? UINavigationController { + return navigationController.viewControllers.last?.topViewController + ?? navigationController + .visibleViewController ?? navigationController + } + if let tabBarController = self as? UITabBarController { + return tabBarController.selectedViewController?.topViewController ?? tabBarController + } + if let presented = presentedViewController { + return presented.topViewController + } + return self + } +} diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h deleted file mode 100644 index 3a63e0722713..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h +++ /dev/null @@ -1,40 +0,0 @@ -// 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 (v9.2.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -#import - -@protocol FlutterBinaryMessenger; -@protocol FlutterMessageCodec; -@class FlutterError; -@class FlutterStandardTypedData; - -NS_ASSUME_NONNULL_BEGIN - -/// The codec used by FULUrlLauncherApi. -NSObject *FULUrlLauncherApiGetCodec(void); - -@protocol FULUrlLauncherApi -/// Returns true if the URL can definitely be launched. -/// -/// @return `nil` only when `error != nil`. -- (nullable NSNumber *)canLaunchURL:(NSString *)url error:(FlutterError *_Nullable *_Nonnull)error; -/// Opens the URL externally, returning true if successful. -- (void)launchURL:(NSString *)url - universalLinksOnly:(NSNumber *)universalLinksOnly - completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -/// Opens the URL in an in-app SFSafariViewController, returning true -/// when it has loaded successfully. -- (void)openSafariViewControllerWithURL:(NSString *)url - completion: - (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; -/// Closes the view controller opened by [openUrlInSafariViewController]. -- (void)closeSafariViewControllerWithError:(FlutterError *_Nullable *_Nonnull)error; -@end - -extern void FULUrlLauncherApiSetup(id binaryMessenger, - NSObject *_Nullable api); - -NS_ASSUME_NONNULL_END diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m deleted file mode 100644 index 4a38efbe4f0d..000000000000 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m +++ /dev/null @@ -1,126 +0,0 @@ -// 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 (v9.2.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -#import "messages.g.h" -#import - -#if !__has_feature(objc_arc) -#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 id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { - id result = array[key]; - return (result == [NSNull null]) ? nil : result; -} - -NSObject *FULUrlLauncherApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - -void FULUrlLauncherApiSetup(id binaryMessenger, - NSObject *api) { - /// Returns true if the URL can definitely be launched. - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl" - binaryMessenger:binaryMessenger - codec:FULUrlLauncherApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(canLaunchURL:error:)], - @"FULUrlLauncherApi api (%@) doesn't respond to @selector(canLaunchURL:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_url = GetNullableObjectAtIndex(args, 0); - FlutterError *error; - NSNumber *output = [api canLaunchURL:arg_url error:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - /// Opens the URL externally, returning true if successful. - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.UrlLauncherApi.launchUrl" - binaryMessenger:binaryMessenger - codec:FULUrlLauncherApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(launchURL:universalLinksOnly:completion:)], - @"FULUrlLauncherApi api (%@) doesn't respond to " - @"@selector(launchURL:universalLinksOnly:completion:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_url = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_universalLinksOnly = GetNullableObjectAtIndex(args, 1); - [api launchURL:arg_url - universalLinksOnly:arg_universalLinksOnly - completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { - callback(wrapResult(output, error)); - }]; - }]; - } else { - [channel setMessageHandler:nil]; - } - } - /// Opens the URL in an in-app SFSafariViewController, returning true - /// when it has loaded successfully. - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.UrlLauncherApi.openUrlInSafariViewController" - binaryMessenger:binaryMessenger - codec:FULUrlLauncherApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(openSafariViewControllerWithURL:completion:)], - @"FULUrlLauncherApi api (%@) doesn't respond to " - @"@selector(openSafariViewControllerWithURL:completion:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_url = GetNullableObjectAtIndex(args, 0); - [api openSafariViewControllerWithURL:arg_url - completion:^(NSNumber *_Nullable output, - FlutterError *_Nullable error) { - callback(wrapResult(output, error)); - }]; - }]; - } else { - [channel setMessageHandler:nil]; - } - } - /// Closes the view controller opened by [openUrlInSafariViewController]. - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.UrlLauncherApi.closeSafariViewController" - binaryMessenger:binaryMessenger - codec:FULUrlLauncherApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(closeSafariViewControllerWithError:)], - @"FULUrlLauncherApi api (%@) doesn't respond to " - @"@selector(closeSafariViewControllerWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api closeSafariViewControllerWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } -} diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift new file mode 100644 index 000000000000..c3b0b8a65df1 --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift @@ -0,0 +1,163 @@ +// 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 (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Possible outcomes of launching a URL. +enum LaunchResult: Int { + /// The URL was successfully launched (or could be, for `canLaunchUrl`). + case success = 0 + /// There was no handler available for the URL. + case failure = 1 + /// The URL could not be launched because it is invalid. + case invalidUrl = 2 +} + +/// Possible outcomes of handling a URL within the application. +enum InAppLoadResult: Int { + /// The URL was successfully loaded. + case success = 0 + /// The URL did not load successfully. + case failedToLoad = 1 + /// The URL could not be launched because it is invalid. + case invalidUrl = 2 +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol UrlLauncherApi { + /// Checks whether a URL can be loaded. + func canLaunchUrl(url: String) throws -> LaunchResult + /// Opens the URL externally, returning the status of launching it. + func launchUrl( + url: String, universalLinksOnly: Bool, + completion: @escaping (Result) -> Void) + /// Opens the URL in an in-app SFSafariViewController, returning the results + /// of loading it. + func openUrlInSafariViewController( + url: String, completion: @escaping (Result) -> Void) + /// Closes the view controller opened by [openUrlInSafariViewController]. + func closeSafariViewController() throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class UrlLauncherApiSetup { + /// The codec used by UrlLauncherApi. + /// Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UrlLauncherApi?) { + /// Checks whether a URL can be loaded. + let canLaunchUrlChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl", + binaryMessenger: binaryMessenger) + if let api = api { + canLaunchUrlChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + do { + let result = try api.canLaunchUrl(url: urlArg) + reply(wrapResult(result.rawValue)) + } catch { + reply(wrapError(error)) + } + } + } else { + canLaunchUrlChannel.setMessageHandler(nil) + } + /// Opens the URL externally, returning the status of launching it. + let launchUrlChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.launchUrl", + binaryMessenger: binaryMessenger) + if let api = api { + launchUrlChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + let universalLinksOnlyArg = args[1] as! Bool + api.launchUrl(url: urlArg, universalLinksOnly: universalLinksOnlyArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res.rawValue)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + launchUrlChannel.setMessageHandler(nil) + } + /// Opens the URL in an in-app SFSafariViewController, returning the results + /// of loading it. + let openUrlInSafariViewControllerChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.openUrlInSafariViewController", + binaryMessenger: binaryMessenger) + if let api = api { + openUrlInSafariViewControllerChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + api.openUrlInSafariViewController(url: urlArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res.rawValue)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + openUrlInSafariViewControllerChannel.setMessageHandler(nil) + } + /// Closes the view controller opened by [openUrlInSafariViewController]. + let closeSafariViewControllerChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController", + binaryMessenger: binaryMessenger) + if let api = api { + closeSafariViewControllerChannel.setMessageHandler { _, reply in + do { + try api.closeSafariViewController() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + closeSafariViewControllerChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec index 3dd3eb97c95b..400ad7384ed8 100644 --- a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec +++ b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec @@ -14,7 +14,7 @@ A Flutter plugin for making the underlying platform (Android or iOS) launch a UR s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios' } s.documentation_url = 'https://pub.dev/packages/url_launcher' s.swift_version = '5.0' - s.source_files = 'Classes/**/*' + s.source_files = 'Classes/**/*.swift' s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', diff --git a/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart index 562a408bd1c1..a7e9a8c6e5ae 100644 --- a/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_ios/lib/src/messages.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 (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v11.0.1), 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,6 +11,30 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +/// Possible outcomes of launching a URL. +enum LaunchResult { + /// The URL was successfully launched (or could be, for `canLaunchUrl`). + success, + + /// There was no handler available for the URL. + failure, + + /// The URL could not be launched because it is invalid. + invalidUrl, +} + +/// Possible outcomes of handling a URL within the application. +enum InAppLoadResult { + /// The URL was successfully loaded. + success, + + /// The URL did not load successfully. + failedToLoad, + + /// The URL could not be launched because it is invalid. + invalidUrl, +} + class UrlLauncherApi { /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -21,10 +45,11 @@ class UrlLauncherApi { static const MessageCodec codec = StandardMessageCodec(); - /// Returns true if the URL can definitely be launched. - Future canLaunchUrl(String arg_url) async { + /// Checks whether a URL can be loaded. + Future canLaunchUrl(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url]) as List?; @@ -45,14 +70,15 @@ class UrlLauncherApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as bool?)!; + return LaunchResult.values[replyList[0]! as int]; } } - /// Opens the URL externally, returning true if successful. - Future launchUrl(String arg_url, bool arg_universalLinksOnly) async { + /// Opens the URL externally, returning the status of launching it. + Future launchUrl( + String arg_url, bool arg_universalLinksOnly) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.launchUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_url, arg_universalLinksOnly]) as List?; @@ -73,15 +99,15 @@ class UrlLauncherApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as bool?)!; + return LaunchResult.values[replyList[0]! as int]; } } - /// Opens the URL in an in-app SFSafariViewController, returning true - /// when it has loaded successfully. - Future openUrlInSafariViewController(String arg_url) async { + /// Opens the URL in an in-app SFSafariViewController, returning the results + /// of loading it. + Future openUrlInSafariViewController(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.openUrlInSafariViewController', + 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.openUrlInSafariViewController', codec, binaryMessenger: _binaryMessenger); final List? replyList = @@ -103,14 +129,15 @@ class UrlLauncherApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as bool?)!; + return InAppLoadResult.values[replyList[0]! as int]; } } /// Closes the view controller opened by [openUrlInSafariViewController]. Future closeSafariViewController() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.closeSafariViewController', codec, + 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { diff --git a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart index 2f0e9f47b940..9d1ebc9c2361 100644 --- a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart +++ b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart' show visibleForTesting; +import 'package:flutter/services.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; @@ -26,8 +27,9 @@ class UrlLauncherIOS extends UrlLauncherPlatform { final LinkDelegate? linkDelegate = null; @override - Future canLaunch(String url) { - return _hostApi.canLaunchUrl(url); + Future canLaunch(String url) async { + final LaunchResult result = await _hostApi.canLaunchUrl(url); + return _mapLaunchResult(result); } @override @@ -45,11 +47,129 @@ class UrlLauncherIOS extends UrlLauncherPlatform { required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, - }) { + }) async { + final PreferredLaunchMode mode; if (useSafariVC) { - return _hostApi.openUrlInSafariViewController(url); + mode = PreferredLaunchMode.inAppBrowserView; + } else if (universalLinksOnly) { + mode = PreferredLaunchMode.externalNonBrowserApplication; } else { - return _hostApi.launchUrl(url, universalLinksOnly); + mode = PreferredLaunchMode.externalApplication; } + return launchUrl( + url, + LaunchOptions( + mode: mode, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: enableDomStorage, + enableJavaScript: enableJavaScript, + headers: headers))); + } + + @override + Future launchUrl(String url, LaunchOptions options) async { + final bool inApp; + switch (options.mode) { + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + // The iOS implementation doesn't distinguish between these two modes; + // both are treated as inAppBrowserView. + inApp = true; + break; + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + inApp = false; + break; + case PreferredLaunchMode.platformDefault: + // Intentionally treat any new values as platformDefault; support for any + // new mode requires intentional opt-in, otherwise falling back is the + // documented behavior. + // ignore: no_default_cases + default: + // By default, open web URLs in the application. + inApp = url.startsWith('http:') || url.startsWith('https:'); + break; + } + + if (inApp) { + return _mapInAppLoadResult( + await _hostApi.openUrlInSafariViewController(url), + url: url); + } else { + return _mapLaunchResult(await _hostApi.launchUrl(url, + options.mode == PreferredLaunchMode.externalNonBrowserApplication)); + } + } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + switch (mode) { + case PreferredLaunchMode.platformDefault: + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + return true; + // Default is a desired behavior here since support for new modes is + // always opt-in, and the enum lives in a different package, so silently + // adding "false" for new values is the correct behavior. + // ignore: no_default_cases + default: + return false; + } + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.inAppWebView || + mode == PreferredLaunchMode.inAppBrowserView; + } + + bool _mapLaunchResult(LaunchResult result) { + switch (result) { + case LaunchResult.success: + return true; + case LaunchResult.failure: + return false; + case LaunchResult.invalidUrl: + throw _invalidUrlException(); + } + } + + bool _mapInAppLoadResult(InAppLoadResult result, {required String url}) { + switch (result) { + case InAppLoadResult.success: + return true; + case InAppLoadResult.failedToLoad: + throw _failedSafariViewControllerLoadException(url); + case InAppLoadResult.invalidUrl: + throw _invalidUrlException(); + } + } + + // TODO(stuartmorgan): Remove this as part of standardizing error handling. + // See https://github.com/flutter/flutter/issues/127665 + // + // This PlatformException (including the exact string details, since those + // are a defacto part of the API) is for compatibility with the previous + // native implementation. + PlatformException _invalidUrlException() { + throw PlatformException( + code: 'argument_error', + message: 'Unable to parse URL', + ); + } + + // TODO(stuartmorgan): Remove this as part of standardizing error handling. + // See https://github.com/flutter/flutter/issues/127665 + // + // This PlatformException (including the exact string details, since those + // are a defacto part of the API) is for compatibility with the previous + // native implementation. + PlatformException _failedSafariViewControllerLoadException(String url) { + throw PlatformException( + code: 'Error', + message: 'Error while launching $url', + ); } } diff --git a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart index f6935cbd8821..f5dc1052b320 100644 --- a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart @@ -6,27 +6,50 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - objcOptions: ObjcOptions(prefix: 'FUL'), - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + swiftOut: 'ios/Classes/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) + +/// Possible outcomes of launching a URL. +enum LaunchResult { + /// The URL was successfully launched (or could be, for `canLaunchUrl`). + success, + + /// There was no handler available for the URL. + failure, + + /// The URL could not be launched because it is invalid. + invalidUrl, +} + +/// Possible outcomes of handling a URL within the application. +enum InAppLoadResult { + /// The URL was successfully loaded. + success, + + /// The URL did not load successfully. + failedToLoad, + + /// The URL could not be launched because it is invalid. + invalidUrl, +} + @HostApi() abstract class UrlLauncherApi { - /// Returns true if the URL can definitely be launched. + /// Checks whether a URL can be loaded. @ObjCSelector('canLaunchURL:') - bool canLaunchUrl(String url); + LaunchResult canLaunchUrl(String url); - /// Opens the URL externally, returning true if successful. + /// Opens the URL externally, returning the status of launching it. @async @ObjCSelector('launchURL:universalLinksOnly:') - bool launchUrl(String url, bool universalLinksOnly); + LaunchResult launchUrl(String url, bool universalLinksOnly); - /// Opens the URL in an in-app SFSafariViewController, returning true - /// when it has loaded successfully. + /// Opens the URL in an in-app SFSafariViewController, returning the results + /// of loading it. @async @ObjCSelector('openSafariViewControllerWithURL:') - bool openUrlInSafariViewController(String url); + InAppLoadResult openUrlInSafariViewController(String url); /// Closes the view controller opened by [openUrlInSafariViewController]. void closeSafariViewController(); diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 8b45ed750549..56b337b47ede 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.1.5 +version: 6.2.1 environment: sdk: ">=2.19.0 <4.0.0" @@ -13,18 +13,20 @@ flutter: implements: url_launcher platforms: ios: - pluginClass: FLTURLLauncherPlugin + pluginClass: URLLauncherPlugin dartPluginClass: UrlLauncherIOS dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: + build_runner: ^2.3.3 flutter_test: sdk: flutter - pigeon: ^9.2.4 + mockito: 5.4.2 + pigeon: ^11.0.1 plugin_platform_interface: ^2.0.0 test: ^1.16.3 diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart index 9274173f90b4..195db6302947 100644 --- a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart +++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart @@ -4,47 +4,64 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:url_launcher_ios/src/messages.g.dart'; import 'package:url_launcher_ios/url_launcher_ios.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); +import 'url_launcher_ios_test.mocks.dart'; - group('UrlLauncherIOS', () { - late _FakeUrlLauncherApi api; +// A web URL to use in tests where the specifics of the URL don't matter. +const String _webUrl = 'https://example.com/'; - setUp(() { - api = _FakeUrlLauncherApi(); - }); +@GenerateMocks([UrlLauncherApi]) +void main() { + late MockUrlLauncherApi api; - test('registers instance', () { - UrlLauncherIOS.registerWith(); - expect(UrlLauncherPlatform.instance, isA()); - }); + setUp(() { + api = MockUrlLauncherApi(); + }); + + test('registers instance', () { + UrlLauncherIOS.registerWith(); + expect(UrlLauncherPlatform.instance, isA()); + }); - test('canLaunch success', () async { + group('canLaunch', () { + test('handles success', () async { + when(api.canLaunchUrl(_webUrl)) + .thenAnswer((_) async => LaunchResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect(await launcher.canLaunch('http://example.com/'), true); + expect(await launcher.canLaunch(_webUrl), true); }); - test('canLaunch failure', () async { + test('handles failure', () async { + when(api.canLaunchUrl(_webUrl)) + .thenAnswer((_) async => LaunchResult.failure); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect(await launcher.canLaunch('unknown://scheme'), false); + expect(await launcher.canLaunch(_webUrl), false); }); - test('canLaunch invalid URL passes the PlatformException through', - () async { + test('throws PlatformException for invalid URL', () async { + when(api.canLaunchUrl(_webUrl)) + .thenAnswer((_) async => LaunchResult.invalidUrl); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await expectLater(launcher.canLaunch('invalid://u r l'), - throwsA(isA())); + await expectLater( + launcher.canLaunch(_webUrl), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'argument_error'))); }); + }); - test('launch success', () async { + group('legacy launch', () { + test('handles success', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( - 'http://example.com/', + _webUrl, useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -53,14 +70,16 @@ void main() { headers: const {}, ), true); - expect(api.passedUniversalLinksOnly, false); + verifyNever(api.openUrlInSafariViewController(any)); }); - test('launch failure', () async { + test('handles failure', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.failure); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( - 'unknown://scheme', + _webUrl, useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -69,14 +88,16 @@ void main() { headers: const {}, ), false); - expect(api.passedUniversalLinksOnly, false); + verifyNever(api.openUrlInSafariViewController(any)); }); - test('launch invalid URL passes the PlatformException through', () async { + test('throws PlatformException for invalid URL', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.invalidUrl); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); await expectLater( launcher.launch( - 'invalid://u r l', + _webUrl, useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -84,14 +105,17 @@ void main() { universalLinksOnly: false, headers: const {}, ), - throwsA(isA())); + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'argument_error'))); }); - test('launch force SafariVC', () async { + test('force SafariVC is handled', () async { + when(api.openUrlInSafariViewController(_webUrl)) + .thenAnswer((_) async => InAppLoadResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( - 'http://example.com/', + _webUrl, useSafariVC: true, useWebView: false, enableJavaScript: false, @@ -100,14 +124,16 @@ void main() { headers: const {}, ), true); - expect(api.usedSafariViewController, true); + verifyNever(api.launchUrl(any, any)); }); - test('launch universal links only', () async { + test('universal links only is handled', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( - 'http://example.com/', + _webUrl, useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -116,14 +142,16 @@ void main() { headers: const {}, ), true); - expect(api.passedUniversalLinksOnly, true); + verifyNever(api.openUrlInSafariViewController(any)); }); - test('launch force SafariVC to false', () async { + test('disallowing SafariVC is handled', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( - 'http://example.com/', + _webUrl, useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -132,58 +160,211 @@ void main() { headers: const {}, ), true); - expect(api.usedSafariViewController, false); + verifyNever(api.openUrlInSafariViewController(any)); }); + }); + + test('closeWebView calls through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await launcher.closeWebView(); + verify(api.closeSafariViewController()).called(1); + }); - test('closeWebView default behavior', () async { + group('launch without webview', () { + test('calls through', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.success); final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await launcher.closeWebView(); - expect(api.closed, true); + final bool launched = await launcher.launchUrl( + _webUrl, + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + ); + expect(launched, true); + verifyNever(api.openUrlInSafariViewController(any)); + }); + + test('throws PlatformException for invalid URL', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.invalidUrl); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl( + _webUrl, + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + ), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'argument_error'))); + }); + }); + + group('launch with Safari view controller', () { + test('calls through with inAppWebView', () async { + when(api.openUrlInSafariViewController(_webUrl)) + .thenAnswer((_) async => InAppLoadResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + _webUrl, const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); + expect(launched, true); + verifyNever(api.launchUrl(any, any)); + }); + + test('calls through with inAppBrowserView', () async { + when(api.openUrlInSafariViewController(_webUrl)) + .thenAnswer((_) async => InAppLoadResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl(_webUrl, + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); + expect(launched, true); + verifyNever(api.launchUrl(any, any)); + }); + + test('throws PlatformException for invalid URL', () async { + when(api.openUrlInSafariViewController(_webUrl)) + .thenAnswer((_) async => InAppLoadResult.invalidUrl); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl(_webUrl, + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'argument_error'))); + }); + + test('throws PlatformException for load failure', () async { + when(api.openUrlInSafariViewController(_webUrl)) + .thenAnswer((_) async => InAppLoadResult.failedToLoad); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl(_webUrl, + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA() + .having((PlatformException e) => e.code, 'code', 'Error'))); }); }); -} -/// A fake implementation of the host API that reacts to specific schemes. -/// -/// See _isLaunchable for the behaviors. -class _FakeUrlLauncherApi implements UrlLauncherApi { - bool? passedUniversalLinksOnly; - bool? usedSafariViewController; - bool? closed; - - @override - Future canLaunchUrl(String url) async { - return _isLaunchable(url); - } - - @override - Future launchUrl(String url, bool universalLinksOnly) async { - passedUniversalLinksOnly = universalLinksOnly; - usedSafariViewController = false; - return _isLaunchable(url); - } - - @override - Future openUrlInSafariViewController(String url) async { - usedSafariViewController = true; - return _isLaunchable(url); - } - - @override - Future closeSafariViewController() async { - closed = true; - } - - bool _isLaunchable(String url) { - final String scheme = url.split(':')[0]; - switch (scheme) { - case 'http': - case 'https': - return true; - case 'invalid': - throw PlatformException(code: 'argument_error'); - default: - return false; - } - } + group('launch with universal links', () { + test('calls through', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + _webUrl, + const LaunchOptions( + mode: PreferredLaunchMode.externalNonBrowserApplication), + ); + expect(launched, true); + verifyNever(api.openUrlInSafariViewController(any)); + }); + + test('throws PlatformException for invalid URL', () async { + when(api.launchUrl(_webUrl, any)) + .thenAnswer((_) async => LaunchResult.invalidUrl); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl( + _webUrl, + const LaunchOptions( + mode: PreferredLaunchMode.externalNonBrowserApplication)), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'argument_error'))); + }); + }); + + group('launch with platform default', () { + test('uses Safari view controller for http', () async { + const String httpUrl = 'http://example.com/'; + when(api.openUrlInSafariViewController(httpUrl)) + .thenAnswer((_) async => InAppLoadResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = + await launcher.launchUrl(httpUrl, const LaunchOptions()); + expect(launched, true); + verifyNever(api.launchUrl(any, any)); + }); + + test('uses Safari view controller for https', () async { + const String httpsUrl = 'https://example.com/'; + when(api.openUrlInSafariViewController(httpsUrl)) + .thenAnswer((_) async => InAppLoadResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = + await launcher.launchUrl(httpsUrl, const LaunchOptions()); + expect(launched, true); + verifyNever(api.launchUrl(any, any)); + }); + + test('uses standard external for other schemes', () async { + const String nonWebUrl = 'supportedcustomscheme://example.com/'; + when(api.launchUrl(nonWebUrl, any)) + .thenAnswer((_) async => LaunchResult.success); + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = + await launcher.launchUrl(nonWebUrl, const LaunchOptions()); + expect(launched, true); + verifyNever(api.openUrlInSafariViewController(any)); + }); + }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns true for external non-browser application', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), + true); + }); + + test('returns true for in app web view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); + }); + + test('returns true for in app browser view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + }); + + group('supportsCloseForMode', () { + test('returns true for in app web view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), + true); + }); + + test('returns true for in app browser view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher.supportsCloseForMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + }); + }); } diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart new file mode 100644 index 000000000000..e9eccab16213 --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart @@ -0,0 +1,79 @@ +// Mocks generated by Mockito 5.4.2 from annotations +// in url_launcher_ios/test/url_launcher_ios_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:url_launcher_ios/src/messages.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: 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 [UrlLauncherApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUrlLauncherApi extends _i1.Mock implements _i2.UrlLauncherApi { + MockUrlLauncherApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future<_i2.LaunchResult> canLaunchUrl(String? arg_url) => + (super.noSuchMethod( + Invocation.method( + #canLaunchUrl, + [arg_url], + ), + returnValue: + _i3.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success), + ) as _i3.Future<_i2.LaunchResult>); + + @override + _i3.Future<_i2.LaunchResult> launchUrl( + String? arg_url, + bool? arg_universalLinksOnly, + ) => + (super.noSuchMethod( + Invocation.method( + #launchUrl, + [ + arg_url, + arg_universalLinksOnly, + ], + ), + returnValue: + _i3.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success), + ) as _i3.Future<_i2.LaunchResult>); + + @override + _i3.Future<_i2.InAppLoadResult> openUrlInSafariViewController( + String? arg_url) => + (super.noSuchMethod( + Invocation.method( + #openUrlInSafariViewController, + [arg_url], + ), + returnValue: + _i3.Future<_i2.InAppLoadResult>.value(_i2.InAppLoadResult.success), + ) as _i3.Future<_i2.InAppLoadResult>); + + @override + _i3.Future closeSafariViewController() => (super.noSuchMethod( + Invocation.method( + #closeSafariViewController, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index a7f308737b4c..538fb7737a36 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.6 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index 0dd4a4cf8a3f..92aff2e3d2c3 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -16,7 +16,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: ../ - url_launcher_platform_interface: ^2.0.0 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart index 87ef3142e3f6..286ac923ce9b 100644 --- a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart +++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart @@ -51,4 +51,16 @@ class UrlLauncherLinux extends UrlLauncherPlatform { }, ).then((bool? value) => value ?? false); } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 2649beeecb99..315ecd2ffb21 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.6 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart index 4e62cc446199..c7e6c8e328c6 100644 --- a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart +++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart @@ -10,7 +10,7 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$UrlLauncherLinux', () { + group('UrlLauncherLinux', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_linux'); final List log = []; @@ -142,6 +142,47 @@ void main() { expect(launched, false); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), + false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); }); } diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index e2f5c6855ed1..ac8a05af5c9e 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.7 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index 0ad239027827..3c98f474d2fe 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -16,7 +16,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: ../ - url_launcher_platform_interface: ^2.0.0 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart index 55c07b798cd8..1d229737c34c 100644 --- a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart +++ b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart @@ -57,6 +57,18 @@ class UrlLauncherMacOS extends UrlLauncherPlatform { return result.value; } + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } + Exception _getInvalidUrlException(String url) { // TODO(stuartmorgan): Make this an actual ArgumentError. This should be // coordinated across all platforms as a breaking change to have them all diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 23d5f6a2f474..ba7066c16174 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.7 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart index a7147af7749b..e9cc3c6c6dc9 100644 --- a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart +++ b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart @@ -106,6 +106,47 @@ void main() { throwsA(isA())); }); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), + false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); }); } diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 836afdcde8a8..76979c9c4c61 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 2.1.0 * Adds `launchUrl` implementation. 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 c9091863ad35..994e3b28badf 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 @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; import 'url_launcher_web_test.mocks.dart'; @@ -218,5 +219,30 @@ void main() { }); }); }); + + group('supportsMode', () { + testWidgets('returns true for platformDefault', (WidgetTester _) async { + expect(plugin.supportsMode(PreferredLaunchMode.platformDefault), + completion(isTrue)); + }); + + testWidgets('returns false for other modes', (WidgetTester _) async { + expect(plugin.supportsMode(PreferredLaunchMode.externalApplication), + completion(isFalse)); + expect( + plugin.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + completion(isFalse)); + expect(plugin.supportsMode(PreferredLaunchMode.inAppBrowserView), + completion(isFalse)); + expect(plugin.supportsMode(PreferredLaunchMode.inAppWebView), + completion(isFalse)); + }); + }); + + testWidgets('supportsCloseForMode returns false', (WidgetTester _) async { + expect(plugin.supportsCloseForMode(PreferredLaunchMode.platformDefault), + completion(isFalse)); + }); }); } diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index 9915164471a0..d0964233150f 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -16,6 +16,6 @@ dev_dependencies: integration_test: sdk: flutter mockito: 5.4.1 - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 url_launcher_web: path: ../ diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart index bf96bf2aa293..0dd1012f708c 100644 --- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart +++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart @@ -111,4 +111,17 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { final String? windowName = options.webOnlyWindowName; return openNewWindow(url, webOnlyWindowName: windowName) != null; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + // Web doesn't allow any control over the destination beyond + // webOnlyWindowName, so don't claim support for any mode beyond default. + return mode == PreferredLaunchMode.platformDefault; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 95dd915d2877..b70a09439e77 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ 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.1.0 +version: 2.2.0 environment: sdk: ">=3.1.0 <4.0.0" @@ -21,7 +21,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - url_launcher_platform_interface: ^2.1.0 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index 933cc1055274..b63c9e19e27c 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.8 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index 77106bc48a74..08bf314b5e1c 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.0 + url_launcher_platform_interface: ^2.2.0 url_launcher_windows: # When depending on this package from a real application you should use: # url_launcher_windows: ^x.y.z diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart index 41c403e56f8e..790a45149be8 100644 --- a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -45,4 +45,16 @@ class UrlLauncherWindows extends UrlLauncherPlatform { // Failure is handled via a PlatformException from `launchUrl`. return true; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index 6b17b9d7c55b..118d928dab3b 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.8 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^2.0.3 + url_launcher_platform_interface: ^2.2.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart index 7f48f64fa92c..0be939bad19b 100644 --- a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart +++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart @@ -77,6 +77,45 @@ void main() { expect(api.argument, 'http://example.com/'); }); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); } class _FakeUrlLauncherApi implements UrlLauncherApi { diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 35e24f355039..690b041661ca 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.1 + +* Updates the example app: replaces `ButtonBar` with `OverflowBar` widget. + ## 2.8.0 * Adds support for macOS. diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index 2691fc2ea55b..7fdd5ae28ce4 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -121,21 +121,26 @@ class _ExampleCard extends StatelessWidget { leading: const Icon(Icons.airline_seat_flat_angled), title: Text(title), ), - ButtonBar( - children: [ - TextButton( - child: const Text('BUY TICKETS'), - onPressed: () { - /* ... */ - }, - ), - TextButton( - child: const Text('SELL TICKETS'), - onPressed: () { - /* ... */ - }, - ), - ], + Padding( + padding: const EdgeInsets.all(8.0), + child: OverflowBar( + alignment: MainAxisAlignment.end, + spacing: 8.0, + children: [ + TextButton( + child: const Text('BUY TICKETS'), + onPressed: () { + /* ... */ + }, + ), + TextButton( + child: const Text('SELL TICKETS'), + onPressed: () { + /* ... */ + }, + ), + ], + ), ), ], ), diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 36fdb3983578..7eab2ac6cdba 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.0 +version: 2.8.1 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 8d744b5a675a..efa0f9658f99 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.1 + +* Updates to Pigeon 13. + ## 2.5.0 * Adds support for macOS. 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 df39593d35dd..dfee03199826 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m @@ -512,7 +512,7 @@ - (int64_t)duration { return FVPCMTimeToMillis([[[_player currentItem] asset] duration]); } -- (void)seekTo:(int)location completionHandler:(void (^)(BOOL))completionHandler { +- (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHandler { CMTime locationCMT = CMTimeMake(location, 1000); CMTimeValue duration = _player.currentItem.asset.duration.value; // Without adding tolerance when seeking to duration, @@ -670,7 +670,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { // https://github.com/flutter/flutter/issues/135320 [registrar publish:instance]; #endif - FVPAVFoundationVideoPlayerApiSetup(registrar.messenger, instance); + SetUpFVPAVFoundationVideoPlayerApi(registrar.messenger, instance); } - (instancetype)initWithRegistrar:(NSObject *)registrar { @@ -709,7 +709,7 @@ - (FVPTextureMessage *)onPlayerSetup:(FVPVideoPlayer *)player [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; self.playersByTextureId[@(textureId)] = player; - FVPTextureMessage *result = [FVPTextureMessage makeWithTextureId:@(textureId)]; + FVPTextureMessage *result = [FVPTextureMessage makeWithTextureId:textureId]; return result; } @@ -761,9 +761,10 @@ - (FVPTextureMessage *)create:(FVPCreateMessage *)input error:(FlutterError **)e } - (void)dispose:(FVPTextureMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; - [self.registry unregisterTexture:input.textureId.intValue]; - [self.playersByTextureId removeObjectForKey:input.textureId]; + NSNumber *playerKey = @(input.textureId); + FVPVideoPlayer *player = self.playersByTextureId[playerKey]; + [self.registry unregisterTexture:input.textureId]; + [self.playersByTextureId removeObjectForKey:playerKey]; // If the Flutter contains https://github.com/flutter/engine/pull/12695, // the `player` is disposed via `onTextureUnregistered` at the right time. // Without https://github.com/flutter/engine/pull/12695, there is no guarantee that the @@ -783,46 +784,46 @@ - (void)dispose:(FVPTextureMessage *)input error:(FlutterError **)error { } - (void)setLooping:(FVPLoopingMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; - player.isLooping = input.isLooping.boolValue; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; + player.isLooping = input.isLooping; } - (void)setVolume:(FVPVolumeMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; - [player setVolume:input.volume.doubleValue]; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; + [player setVolume:input.volume]; } - (void)setPlaybackSpeed:(FVPPlaybackSpeedMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; - [player setPlaybackSpeed:input.speed.doubleValue]; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; + [player setPlaybackSpeed:input.speed]; } - (void)play:(FVPTextureMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; [player play]; } - (FVPPositionMessage *)position:(FVPTextureMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; FVPPositionMessage *result = [FVPPositionMessage makeWithTextureId:input.textureId - position:@([player position])]; + position:[player position]]; return result; } - (void)seekTo:(FVPPositionMessage *)input completion:(void (^)(FlutterError *_Nullable))completion { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; - [player seekTo:input.position.intValue + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; + [player seekTo:input.position completionHandler:^(BOOL finished) { dispatch_async(dispatch_get_main_queue(), ^{ - [self.registry textureFrameAvailable:input.textureId.intValue]; + [self.registry textureFrameAvailable:input.textureId]; completion(nil); }); }]; } - (void)pause:(FVPTextureMessage *)input error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByTextureId[input.textureId]; + FVPVideoPlayer *player = self.playersByTextureId[@(input.textureId)]; [player pause]; } @@ -831,7 +832,7 @@ - (void)setMixWithOthers:(FVPMixWithOthersMessage *)input #if TARGET_OS_OSX // AVAudioSession doesn't exist on macOS, and audio always mixes, so just no-op. #else - if (input.mixWithOthers.boolValue) { + if (input.mixWithOthers) { [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.h b/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.h index c66e5ad9d553..bd28d2c47ec7 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.h +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.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 (v11.0.1), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -24,40 +24,40 @@ NS_ASSUME_NONNULL_BEGIN @interface FVPTextureMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithTextureId:(NSNumber *)textureId; -@property(nonatomic, strong) NSNumber *textureId; ++ (instancetype)makeWithTextureId:(NSInteger)textureId; +@property(nonatomic, assign) NSInteger textureId; @end @interface FVPLoopingMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithTextureId:(NSNumber *)textureId isLooping:(NSNumber *)isLooping; -@property(nonatomic, strong) NSNumber *textureId; -@property(nonatomic, strong) NSNumber *isLooping; ++ (instancetype)makeWithTextureId:(NSInteger)textureId isLooping:(BOOL)isLooping; +@property(nonatomic, assign) NSInteger textureId; +@property(nonatomic, assign) BOOL isLooping; @end @interface FVPVolumeMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithTextureId:(NSNumber *)textureId volume:(NSNumber *)volume; -@property(nonatomic, strong) NSNumber *textureId; -@property(nonatomic, strong) NSNumber *volume; ++ (instancetype)makeWithTextureId:(NSInteger)textureId volume:(double)volume; +@property(nonatomic, assign) NSInteger textureId; +@property(nonatomic, assign) double volume; @end @interface FVPPlaybackSpeedMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithTextureId:(NSNumber *)textureId speed:(NSNumber *)speed; -@property(nonatomic, strong) NSNumber *textureId; -@property(nonatomic, strong) NSNumber *speed; ++ (instancetype)makeWithTextureId:(NSInteger)textureId speed:(double)speed; +@property(nonatomic, assign) NSInteger textureId; +@property(nonatomic, assign) double speed; @end @interface FVPPositionMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithTextureId:(NSNumber *)textureId position:(NSNumber *)position; -@property(nonatomic, strong) NSNumber *textureId; -@property(nonatomic, strong) NSNumber *position; ++ (instancetype)makeWithTextureId:(NSInteger)textureId position:(NSInteger)position; +@property(nonatomic, assign) NSInteger textureId; +@property(nonatomic, assign) NSInteger position; @end @interface FVPCreateMessage : NSObject @@ -72,14 +72,14 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, nullable) NSString *uri; @property(nonatomic, copy, nullable) NSString *packageName; @property(nonatomic, copy, nullable) NSString *formatHint; -@property(nonatomic, strong) NSDictionary *httpHeaders; +@property(nonatomic, copy) NSDictionary *httpHeaders; @end @interface FVPMixWithOthersMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithMixWithOthers:(NSNumber *)mixWithOthers; -@property(nonatomic, strong) NSNumber *mixWithOthers; ++ (instancetype)makeWithMixWithOthers:(BOOL)mixWithOthers; +@property(nonatomic, assign) BOOL mixWithOthers; @end /// The codec used by FVPAVFoundationVideoPlayerApi. @@ -105,7 +105,7 @@ NSObject *FVPAVFoundationVideoPlayerApiGetCodec(void); error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FVPAVFoundationVideoPlayerApiSetup( +extern void SetUpFVPAVFoundationVideoPlayerApi( id binaryMessenger, NSObject *_Nullable api); diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m b/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m index 3d2a73fc6b98..38bd173a07cc 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m +++ b/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.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 (v11.0.1), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" @@ -72,15 +72,14 @@ - (NSArray *)toList; @end @implementation FVPTextureMessage -+ (instancetype)makeWithTextureId:(NSNumber *)textureId { ++ (instancetype)makeWithTextureId:(NSInteger)textureId { FVPTextureMessage *pigeonResult = [[FVPTextureMessage alloc] init]; pigeonResult.textureId = textureId; return pigeonResult; } + (FVPTextureMessage *)fromList:(NSArray *)list { FVPTextureMessage *pigeonResult = [[FVPTextureMessage alloc] init]; - pigeonResult.textureId = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.textureId != nil, @""); + pigeonResult.textureId = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FVPTextureMessage *)nullableFromList:(NSArray *)list { @@ -88,13 +87,13 @@ + (nullable FVPTextureMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.textureId ?: [NSNull null]), + @(self.textureId), ]; } @end @implementation FVPLoopingMessage -+ (instancetype)makeWithTextureId:(NSNumber *)textureId isLooping:(NSNumber *)isLooping { ++ (instancetype)makeWithTextureId:(NSInteger)textureId isLooping:(BOOL)isLooping { FVPLoopingMessage *pigeonResult = [[FVPLoopingMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.isLooping = isLooping; @@ -102,10 +101,8 @@ + (instancetype)makeWithTextureId:(NSNumber *)textureId isLooping:(NSNumber *)is } + (FVPLoopingMessage *)fromList:(NSArray *)list { FVPLoopingMessage *pigeonResult = [[FVPLoopingMessage alloc] init]; - pigeonResult.textureId = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.textureId != nil, @""); - pigeonResult.isLooping = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.isLooping != nil, @""); + pigeonResult.textureId = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.isLooping = [GetNullableObjectAtIndex(list, 1) boolValue]; return pigeonResult; } + (nullable FVPLoopingMessage *)nullableFromList:(NSArray *)list { @@ -113,14 +110,14 @@ + (nullable FVPLoopingMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.textureId ?: [NSNull null]), - (self.isLooping ?: [NSNull null]), + @(self.textureId), + @(self.isLooping), ]; } @end @implementation FVPVolumeMessage -+ (instancetype)makeWithTextureId:(NSNumber *)textureId volume:(NSNumber *)volume { ++ (instancetype)makeWithTextureId:(NSInteger)textureId volume:(double)volume { FVPVolumeMessage *pigeonResult = [[FVPVolumeMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.volume = volume; @@ -128,10 +125,8 @@ + (instancetype)makeWithTextureId:(NSNumber *)textureId volume:(NSNumber *)volum } + (FVPVolumeMessage *)fromList:(NSArray *)list { FVPVolumeMessage *pigeonResult = [[FVPVolumeMessage alloc] init]; - pigeonResult.textureId = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.textureId != nil, @""); - pigeonResult.volume = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.volume != nil, @""); + pigeonResult.textureId = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.volume = [GetNullableObjectAtIndex(list, 1) doubleValue]; return pigeonResult; } + (nullable FVPVolumeMessage *)nullableFromList:(NSArray *)list { @@ -139,14 +134,14 @@ + (nullable FVPVolumeMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.textureId ?: [NSNull null]), - (self.volume ?: [NSNull null]), + @(self.textureId), + @(self.volume), ]; } @end @implementation FVPPlaybackSpeedMessage -+ (instancetype)makeWithTextureId:(NSNumber *)textureId speed:(NSNumber *)speed { ++ (instancetype)makeWithTextureId:(NSInteger)textureId speed:(double)speed { FVPPlaybackSpeedMessage *pigeonResult = [[FVPPlaybackSpeedMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.speed = speed; @@ -154,10 +149,8 @@ + (instancetype)makeWithTextureId:(NSNumber *)textureId speed:(NSNumber *)speed } + (FVPPlaybackSpeedMessage *)fromList:(NSArray *)list { FVPPlaybackSpeedMessage *pigeonResult = [[FVPPlaybackSpeedMessage alloc] init]; - pigeonResult.textureId = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.textureId != nil, @""); - pigeonResult.speed = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.speed != nil, @""); + pigeonResult.textureId = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.speed = [GetNullableObjectAtIndex(list, 1) doubleValue]; return pigeonResult; } + (nullable FVPPlaybackSpeedMessage *)nullableFromList:(NSArray *)list { @@ -165,14 +158,14 @@ + (nullable FVPPlaybackSpeedMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.textureId ?: [NSNull null]), - (self.speed ?: [NSNull null]), + @(self.textureId), + @(self.speed), ]; } @end @implementation FVPPositionMessage -+ (instancetype)makeWithTextureId:(NSNumber *)textureId position:(NSNumber *)position { ++ (instancetype)makeWithTextureId:(NSInteger)textureId position:(NSInteger)position { FVPPositionMessage *pigeonResult = [[FVPPositionMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.position = position; @@ -180,10 +173,8 @@ + (instancetype)makeWithTextureId:(NSNumber *)textureId position:(NSNumber *)pos } + (FVPPositionMessage *)fromList:(NSArray *)list { FVPPositionMessage *pigeonResult = [[FVPPositionMessage alloc] init]; - pigeonResult.textureId = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.textureId != nil, @""); - pigeonResult.position = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.position != nil, @""); + pigeonResult.textureId = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.position = [GetNullableObjectAtIndex(list, 1) integerValue]; return pigeonResult; } + (nullable FVPPositionMessage *)nullableFromList:(NSArray *)list { @@ -191,8 +182,8 @@ + (nullable FVPPositionMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.textureId ?: [NSNull null]), - (self.position ?: [NSNull null]), + @(self.textureId), + @(self.position), ]; } @end @@ -218,7 +209,6 @@ + (FVPCreateMessage *)fromList:(NSArray *)list { pigeonResult.packageName = GetNullableObjectAtIndex(list, 2); pigeonResult.formatHint = GetNullableObjectAtIndex(list, 3); pigeonResult.httpHeaders = GetNullableObjectAtIndex(list, 4); - NSAssert(pigeonResult.httpHeaders != nil, @""); return pigeonResult; } + (nullable FVPCreateMessage *)nullableFromList:(NSArray *)list { @@ -226,25 +216,24 @@ + (nullable FVPCreateMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.asset ?: [NSNull null]), - (self.uri ?: [NSNull null]), - (self.packageName ?: [NSNull null]), - (self.formatHint ?: [NSNull null]), - (self.httpHeaders ?: [NSNull null]), + self.asset ?: [NSNull null], + self.uri ?: [NSNull null], + self.packageName ?: [NSNull null], + self.formatHint ?: [NSNull null], + self.httpHeaders ?: [NSNull null], ]; } @end @implementation FVPMixWithOthersMessage -+ (instancetype)makeWithMixWithOthers:(NSNumber *)mixWithOthers { ++ (instancetype)makeWithMixWithOthers:(BOOL)mixWithOthers { FVPMixWithOthersMessage *pigeonResult = [[FVPMixWithOthersMessage alloc] init]; pigeonResult.mixWithOthers = mixWithOthers; return pigeonResult; } + (FVPMixWithOthersMessage *)fromList:(NSArray *)list { FVPMixWithOthersMessage *pigeonResult = [[FVPMixWithOthersMessage alloc] init]; - pigeonResult.mixWithOthers = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.mixWithOthers != nil, @""); + pigeonResult.mixWithOthers = [GetNullableObjectAtIndex(list, 0) boolValue]; return pigeonResult; } + (nullable FVPMixWithOthersMessage *)nullableFromList:(NSArray *)list { @@ -252,7 +241,7 @@ + (nullable FVPMixWithOthersMessage *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.mixWithOthers ?: [NSNull null]), + @(self.mixWithOthers), ]; } @end @@ -335,7 +324,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FVPAVFoundationVideoPlayerApiSetup(id binaryMessenger, +void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m index 2ef7a2bdc9d5..4b030cc68060 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -141,7 +141,7 @@ - (void)testBlankVideoBugWithEncryptedVideoStreamAndInvertedAspectRatioBugForSom FVPTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(textureMessage); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureMessage.textureId)]; XCTAssertNotNil(player); XCTAssertNotNil(player.playerLayer, @"AVPlayerLayer should be present."); @@ -169,18 +169,18 @@ - (void)testSeekToInvokesTextureFrameAvailableOnTextureRegistry { httpHeaders:@{}]; FlutterError *createError; FVPTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&createError]; - NSNumber *textureId = textureMessage.textureId; + NSInteger textureId = textureMessage.textureId; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"seekTo completes"]; - FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:@1234]; + FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:1234]; [videoPlayerPlugin seekTo:message completion:^(FlutterError *_Nullable error) { [initializedExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; - OCMVerify([mockTextureRegistry textureFrameAvailable:message.textureId.intValue]); + OCMVerify([mockTextureRegistry textureFrameAvailable:message.textureId]); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureId)]; XCTAssertEqual([player position], 1234); } @@ -203,7 +203,7 @@ - (void)testDeregistersFromPlayer { FVPTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(textureMessage); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureMessage.textureId)]; XCTAssertNotNil(player); AVPlayer *avPlayer = player.player; @@ -234,7 +234,7 @@ - (void)testBufferingStateFromPlayer { FVPTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(textureMessage); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureMessage.textureId)]; XCTAssertNotNil(player); AVPlayer *avPlayer = player.player; [avPlayer play]; @@ -340,11 +340,11 @@ - (void)testSeekToleranceWhenNotSeekingToEnd { httpHeaders:@{}]; FlutterError *createError; FVPTextureMessage *textureMessage = [pluginWithMockAVPlayer create:create error:&createError]; - NSNumber *textureId = textureMessage.textureId; + NSInteger textureId = textureMessage.textureId; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"seekTo has zero tolerance when seeking not to end"]; - FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:@1234]; + FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:1234]; [pluginWithMockAVPlayer seekTo:message completion:^(FlutterError *_Nullable error) { [initializedExpectation fulfill]; @@ -377,12 +377,12 @@ - (void)testSeekToleranceWhenSeekingToEnd { httpHeaders:@{}]; FlutterError *createError; FVPTextureMessage *textureMessage = [pluginWithMockAVPlayer create:create error:&createError]; - NSNumber *textureId = textureMessage.textureId; + NSInteger textureId = textureMessage.textureId; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"seekTo has non-zero tolerance when seeking to end"]; // The duration of this video is "0" due to the non standard initiliatazion process. - FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:@0]; + FVPPositionMessage *message = [FVPPositionMessage makeWithTextureId:textureId position:0]; [pluginWithMockAVPlayer seekTo:message completion:^(FlutterError *_Nullable error) { [initializedExpectation fulfill]; @@ -405,8 +405,8 @@ - (void)testSeekToleranceWhenSeekingToEnd { httpHeaders:@{}]; FVPTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; - NSNumber *textureId = textureMessage.textureId; - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId]; + NSInteger textureId = textureMessage.textureId; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureId)]; XCTAssertNotNil(player); XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; @@ -428,15 +428,14 @@ - (void)testSeekToleranceWhenSeekingToEnd { XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused); // Change playback speed. - FVPPlaybackSpeedMessage *playback = [FVPPlaybackSpeedMessage makeWithTextureId:textureId - speed:@2]; + FVPPlaybackSpeedMessage *playback = [FVPPlaybackSpeedMessage makeWithTextureId:textureId speed:2]; [videoPlayerPlugin setPlaybackSpeed:playback error:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.rate, 2); XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate); // Volume - FVPVolumeMessage *volume = [FVPVolumeMessage makeWithTextureId:textureId volume:@0.1]; + FVPVolumeMessage *volume = [FVPVolumeMessage makeWithTextureId:textureId volume:0.1]; [videoPlayerPlugin setVolume:volume error:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.volume, 0.1f); @@ -476,7 +475,7 @@ - (void)testDoesNotCrashOnRateObservationAfterDisposal { XCTAssertNil(error); XCTAssertNotNil(textureMessage); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureMessage.textureId)]; XCTAssertNotNil(player); weakPlayer = player; avPlayer = player.player; @@ -530,7 +529,7 @@ - (void)testHotReloadDoesNotCrash { XCTAssertNil(error); XCTAssertNotNil(textureMessage); - FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[@(textureMessage.textureId)]; XCTAssertNotNil(player); weakPlayer = player; diff --git a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart index f620e22e608c..b00ad0b60d97 100644 --- a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart +++ b/packages/video_player/video_player_avfoundation/lib/src/messages.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 (v11.0.1), 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,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + class TextureMessage { TextureMessage({ required this.textureId, diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index 2aeacefffa64..59e451b8fa9d 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -7,8 +7,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartTestOut: 'test/test_api.g.dart', - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + objcHeaderOut: 'darwin/Classes/messages.g.h', + objcSourceOut: 'darwin/Classes/messages.g.m', objcOptions: ObjcOptions( prefix: 'FVP', ), diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 31e6b1696d85..38c4658f4b8f 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation description: iOS and macOS implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.5.0 +version: 2.5.1 environment: sdk: ">=3.1.0 <4.0.0" @@ -29,7 +29,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - pigeon: ^11.0.1 + pigeon: ^13.0.0 topics: - video diff --git a/packages/video_player/video_player_avfoundation/test/test_api.g.dart b/packages/video_player/video_player_avfoundation/test/test_api.g.dart index 19d0c084ccc8..8aef34af4164 100644 --- a/packages/video_player/video_player_avfoundation/test/test_api.g.dart +++ b/packages/video_player/video_player_avfoundation/test/test_api.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 (v11.0.1), 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 @@ -107,9 +107,15 @@ abstract class TestHostVideoPlayerApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - // ignore message - api.initialize(); - return []; + try { + api.initialize(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -131,8 +137,15 @@ abstract class TestHostVideoPlayerApi { final CreateMessage? arg_msg = (args[0] as CreateMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.create was null, expected non-null CreateMessage.'); - final TextureMessage output = api.create(arg_msg!); - return [output]; + try { + final TextureMessage output = api.create(arg_msg!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -154,8 +167,15 @@ abstract class TestHostVideoPlayerApi { final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.dispose was null, expected non-null TextureMessage.'); - api.dispose(arg_msg!); - return []; + try { + api.dispose(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -177,8 +197,15 @@ abstract class TestHostVideoPlayerApi { final LoopingMessage? arg_msg = (args[0] as LoopingMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping was null, expected non-null LoopingMessage.'); - api.setLooping(arg_msg!); - return []; + try { + api.setLooping(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -200,8 +227,15 @@ abstract class TestHostVideoPlayerApi { final VolumeMessage? arg_msg = (args[0] as VolumeMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume was null, expected non-null VolumeMessage.'); - api.setVolume(arg_msg!); - return []; + try { + api.setVolume(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -224,8 +258,15 @@ abstract class TestHostVideoPlayerApi { (args[0] as PlaybackSpeedMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed was null, expected non-null PlaybackSpeedMessage.'); - api.setPlaybackSpeed(arg_msg!); - return []; + try { + api.setPlaybackSpeed(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -247,8 +288,15 @@ abstract class TestHostVideoPlayerApi { final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.play was null, expected non-null TextureMessage.'); - api.play(arg_msg!); - return []; + try { + api.play(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -270,8 +318,15 @@ abstract class TestHostVideoPlayerApi { final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.position was null, expected non-null TextureMessage.'); - final PositionMessage output = api.position(arg_msg!); - return [output]; + try { + final PositionMessage output = api.position(arg_msg!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -293,8 +348,15 @@ abstract class TestHostVideoPlayerApi { final PositionMessage? arg_msg = (args[0] as PositionMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo was null, expected non-null PositionMessage.'); - await api.seekTo(arg_msg!); - return []; + try { + await api.seekTo(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -316,8 +378,15 @@ abstract class TestHostVideoPlayerApi { final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause was null, expected non-null TextureMessage.'); - api.pause(arg_msg!); - return []; + try { + api.pause(arg_msg!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -340,8 +409,15 @@ abstract class TestHostVideoPlayerApi { (args[0] as MixWithOthersMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers was null, expected non-null MixWithOthersMessage.'); - api.setMixWithOthers(arg_msg!); - return []; + try { + api.setMixWithOthers(arg_msg!); + 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/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 5e02936f971a..62d0536cd8e9 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.2 + +* Listens to `loadedmetadata` as an event that marks that initialization is + complete. (Fixes playback in Safari iOS 17). +* Sets the `src` of the underlying video element after every other attribute. + ## 2.1.1 * Ensures that the `autoplay` attribute of the underlying video element is set 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 831b5a388764..01f8e2f34357 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 @@ -197,6 +197,23 @@ void main() { expect(events[0].eventType, VideoEventType.initialized); }); + // 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')); + + final Future> stream = timedStream + .where((VideoEvent event) => + event.eventType == VideoEventType.initialized) + .toList(); + + final List events = await stream; + + expect(events, hasLength(1)); + expect(events[0].eventType, VideoEventType.initialized); + }); + // Issue: https://github.com/flutter/flutter/issues/105649 testWidgets('supports `Infinity` duration', (WidgetTester _) async { setInfinityDuration(video); 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 75322a04c165..4adb2e1e8662 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 @@ -58,7 +58,17 @@ class VideoPlayer { /// This method sets the required DOM attributes so videos can [play] programmatically, /// and attaches listeners to the internal events from the [html.VideoElement] /// to react to them / expose them through the [VideoPlayer.events] stream. - void initialize() { + /// + /// The [src] parameter is the URL of the video. It is passed in from the plugin + /// `create` method so it can be set in the VideoElement *last*. This way, all + /// the event listeners needed to integrate the videoElement with the plugin + /// are attached before any events start firing (events start to fire when the + /// `src` attribute is set). + /// + /// The `src` parameter is nullable for testing purposes. + void initialize({ + String? src, + }) { _videoElement ..autoplay = false ..controls = false; @@ -68,14 +78,11 @@ class VideoPlayer { // 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'); + _videoElement.setAttribute('playsinline', true); - _videoElement.onCanPlay.listen((dynamic _) { - if (!_isInitialized) { - _isInitialized = true; - _sendInitialized(); - } - }); + _videoElement.onCanPlay.listen(_onVideoElementInitialization); + // Needed for Safari iOS 17, which may not send `canplay`. + _videoElement.onLoadedMetadata.listen(_onVideoElementInitialization); _videoElement.onCanPlayThrough.listen((dynamic _) { setBuffering(false); @@ -122,6 +129,12 @@ class VideoPlayer { setBuffering(false); _eventController.add(VideoEvent(eventType: VideoEventType.completed)); }); + + // The `src` of the _videoElement is the last property that is set, so all + // the listeners for the events that the plugin cares about are attached. + if (src != null) { + _videoElement.src = src; + } } /// Attempts to play the video. @@ -252,6 +265,20 @@ class VideoPlayer { _videoElement.load(); } + // Handler to mark (and broadcast) when this player [_isInitialized]. + // + // (Used as a JS event handler for "canplay" and "loadedmetadata") + // + // This function can be called multiple times by different JS Events, but it'll + // only broadcast an "initialized" event the first time it's called, and ignore + // the rest of the calls. + void _onVideoElementInitialization(Object? _) { + if (!_isInitialized) { + _isInitialized = true; + _sendInitialized(); + } + } + // Sends an [VideoEventType.initialized] [VideoEvent] with info about the wrapped video. void _sendInitialized() { final Duration? duration = 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 77b3cca2804b..9fe07d1204e1 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 @@ -75,7 +75,6 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { final VideoElement videoElement = VideoElement() ..id = 'videoElement-$textureId' - ..src = uri ..style.border = 'none' ..style.height = '100%' ..style.width = '100%'; @@ -85,7 +84,9 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { 'videoPlayer-$textureId', (int viewId) => videoElement); final VideoPlayer player = VideoPlayer(videoElement: videoElement) - ..initialize(); + ..initialize( + src: uri, + ); _videoPlayers[textureId] = player; diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 19a7aac202a0..8b312aef3853 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -2,7 +2,7 @@ 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.1 +version: 2.1.2 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 9f8374332e9d..4fc35f8d05fb 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.4.2 + +* Fixes `use_build_context_synchronously` lint violations in the example app. + ## 4.4.1 * Exposes `JavaScriptLogLevel` from platform interface. diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index 96cbbf8f7e36..d23f4ce96fb9 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -217,7 +217,7 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - if (context.mounted) { + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 75b9efcc23a4..0068d95d22f9 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.4.1 +version: 4.4.2 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 12f7ba007d26..e59405cfdf02 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.12.1 + +* Fixes `use_build_context_synchronously` lint violations in the example app. + ## 3.12.0 * Adds support for `PlatformWebViewController.getUserAgent`. 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 92a8fe2bd770..68794e4157e3 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -210,7 +210,7 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - if (context.mounted) { + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 8087d7957489..701c124e59ac 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.12.0 +version: 3.12.1 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 4de08406bba1..9c64dc5abd29 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,16 @@ +## 3.9.4 + +* Updates to Pigeon 13. + +## 3.9.3 + +* Fixes `use_build_context_synchronously` lint violations in the example app. + +## 3.9.2 + +* Fixes error caused by calling `WKWebViewConfiguration.limitsNavigationsToAppBoundDomains` on + versions below 14. + ## 3.9.1 * Fixes bug where `WebkitWebViewController.getUserAgent` was incorrectly returning an empty String. 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 1a64148b4868..7613bf02f75c 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 @@ -49,7 +49,7 @@ - (void)testFWFWKUserScriptFromScriptData { makeWithSource:@"mySource" injectionTime:[FWFWKUserScriptInjectionTimeEnumData makeWithValue:FWFWKUserScriptInjectionTimeEnumAtDocumentStart] - isMainFrameOnly:@NO]); + isMainFrameOnly:NO]); XCTAssertEqualObjects(userScript.source, @"mySource"); XCTAssertEqual(userScript.injectionTime, WKUserScriptInjectionTimeAtDocumentStart); @@ -94,7 +94,7 @@ - (void)testFWFWKFrameInfoDataFromWKFrameInfo { OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); FWFWKFrameInfoData *targetFrameData = FWFWKFrameInfoDataFromNativeWKFrameInfo(mockFrameInfo); - XCTAssertEqualObjects(targetFrameData.isMainFrame, @YES); + XCTAssertEqual(targetFrameData.isMainFrame, YES); } - (void)testFWFNSErrorDataFromNSError { @@ -104,7 +104,7 @@ - (void)testFWFNSErrorDataFromNSError { userInfo:@{@"a" : @"b", @"c" : unsupportedType}]; FWFNSErrorData *data = FWFNSErrorDataFromNativeNSError(error); - XCTAssertEqualObjects(data.code, @23); + XCTAssertEqual(data.code, 23); XCTAssertEqualObjects(data.domain, @"domain"); NSDictionary *userInfo = @{ @@ -133,7 +133,7 @@ - (void)testFWFWKSecurityOriginDataFromWKSecurityOrigin { FWFWKSecurityOriginData *data = FWFWKSecurityOriginDataFromNativeWKSecurityOrigin(mockSecurityOrigin); XCTAssertEqualObjects(data.host, @"host"); - XCTAssertEqualObjects(data.port, @(2)); + XCTAssertEqual(data.port, 2); XCTAssertEqualObjects(data.protocol, @"protocol"); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFHTTPCookieStoreHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFHTTPCookieStoreHostApiTests.m index 185b4804371e..478e4fedef01 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFHTTPCookieStoreHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFHTTPCookieStoreHostApiTests.m @@ -22,7 +22,7 @@ - (void)testCreateFromWebsiteDataStoreWithIdentifier { [instanceManager addDartCreatedInstance:mockDataStore withIdentifier:0]; FlutterError *error; - [hostAPI createFromWebsiteDataStoreWithIdentifier:@1 dataStoreIdentifier:@0 error:&error]; + [hostAPI createFromWebsiteDataStoreWithIdentifier:1 dataStoreIdentifier:0 error:&error]; WKHTTPCookieStore *cookieStore = (WKHTTPCookieStore *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([cookieStore isKindOfClass:[WKHTTPCookieStore class]]); XCTAssertNil(error); @@ -42,7 +42,7 @@ - (void)testSetCookie { makeWithValue:FWFNSHttpCookiePropertyKeyEnumName] ] propertyValues:@[ @"hello" ]]; FlutterError *__block blockError; - [hostAPI setCookieForStoreWithIdentifier:@0 + [hostAPI setCookieForStoreWithIdentifier:0 cookie:cookieData completion:^(FlutterError *error) { blockError = error; 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 570a1f73ad9b..669234d69b3c 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 @@ -51,7 +51,7 @@ - (void)testCreateWithIdentifier { instanceManager:instanceManager]; FlutterError *error; - [hostAPI createWithIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:0 error:&error]; FWFNavigationDelegate *navigationDelegate = (FWFNavigationDelegate *)[instanceManager instanceForIdentifier:0]; @@ -74,8 +74,8 @@ - (void)testDidFinishNavigation { [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webView:mockWebView didFinishNavigation:OCMClassMock([WKNavigation class])]; - OCMVerify([mockFlutterAPI didFinishNavigationForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + OCMVerify([mockFlutterAPI didFinishNavigationForDelegateWithIdentifier:0 + webViewIdentifier:1 URL:@"https://flutter.dev/" completion:OCMOCK_ANY]); } @@ -97,8 +97,8 @@ - (void)testDidStartProvisionalNavigation { [mockDelegate webView:mockWebView didStartProvisionalNavigation:OCMClassMock([WKNavigation class])]; OCMVerify([mockFlutterAPI - didStartProvisionalNavigationForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + didStartProvisionalNavigationForDelegateWithIdentifier:0 + webViewIdentifier:1 URL:@"https://flutter.dev/" completion:OCMOCK_ANY]); } @@ -125,8 +125,8 @@ - (void)testDecidePolicyForNavigationAction { OCMStub([mockNavigationAction targetFrame]).andReturn(mockFrameInfo); OCMStub([mockFlutterAPI - decidePolicyForNavigationActionForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + decidePolicyForNavigationActionForDelegateWithIdentifier:0 + webViewIdentifier:1 navigationAction: [OCMArg isKindOfClass:[FWFWKNavigationActionData class]] @@ -164,8 +164,8 @@ - (void)testDidFailNavigation { didFailNavigation:OCMClassMock([WKNavigation class]) withError:[NSError errorWithDomain:@"domain" code:0 userInfo:nil]]; OCMVerify([mockFlutterAPI - didFailNavigationForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + didFailNavigationForDelegateWithIdentifier:0 + webViewIdentifier:1 error:[OCMArg isKindOfClass:[FWFNSErrorData class]] completion:OCMOCK_ANY]); } @@ -187,8 +187,8 @@ - (void)testDidFailProvisionalNavigation { didFailProvisionalNavigation:OCMClassMock([WKNavigation class]) withError:[NSError errorWithDomain:@"domain" code:0 userInfo:nil]]; OCMVerify([mockFlutterAPI - didFailProvisionalNavigationForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + didFailProvisionalNavigationForDelegateWithIdentifier:0 + webViewIdentifier:1 error:[OCMArg isKindOfClass:[FWFNSErrorData class]] completion:OCMOCK_ANY]); @@ -209,8 +209,8 @@ - (void)testWebViewWebContentProcessDidTerminate { [mockDelegate webViewWebContentProcessDidTerminate:mockWebView]; OCMVerify([mockFlutterAPI - webViewWebContentProcessDidTerminateForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + webViewWebContentProcessDidTerminateForDelegateWithIdentifier:0 + webViewIdentifier:1 completion:OCMOCK_ANY]); } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m index 888a3a1e9a66..8c0b90850a82 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m @@ -57,8 +57,8 @@ - (void)testAddObserver { FlutterError *error; [hostAPI - addObserverForObjectWithIdentifier:@0 - observerIdentifier:@1 + addObserverForObjectWithIdentifier:0 + observerIdentifier:1 keyPath:@"myKey" options:@[ [FWFNSKeyValueObservingOptionsEnumData @@ -88,8 +88,8 @@ - (void)testRemoveObserver { [instanceManager addDartCreatedInstance:observerObject withIdentifier:1]; FlutterError *error; - [hostAPI removeObserverForObjectWithIdentifier:@0 - observerIdentifier:@1 + [hostAPI removeObserverForObjectWithIdentifier:0 + observerIdentifier:1 keyPath:@"myKey" error:&error]; OCMVerify([mockObject removeObserver:observerObject forKeyPath:@"myKey"]); @@ -106,7 +106,7 @@ - (void)testDispose { [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI disposeObjectWithIdentifier:@0 error:&error]; + [hostAPI disposeObjectWithIdentifier:0 error:&error]; // Only the strong reference is removed, so the weak reference will remain until object is set to // nil. object = nil; @@ -130,9 +130,9 @@ - (void)testObserveValueForKeyPath { change:@{NSKeyValueChangeOldKey : @"key"} context:nil]; OCMVerify([mockFlutterAPI - observeValueForObjectWithIdentifier:@0 + observeValueForObjectWithIdentifier:0 keyPath:@"keyPath" - objectIdentifier:@1 + objectIdentifier:1 changeKeys:[OCMArg checkWithBlock:^BOOL( NSArray *value) { @@ -140,8 +140,7 @@ - (void)testObserveValueForKeyPath { }] changeValues:[OCMArg checkWithBlock:^BOOL(id value) { FWFObjectOrIdentifier *object = (FWFObjectOrIdentifier *)value[0]; - return !object.isIdentifier.boolValue && - [@"key" isEqual:object.value]; + return !object.isIdentifier && [@"key" isEqual:object.value]; }] completion:OCMOCK_ANY]); } @@ -165,9 +164,9 @@ - (void)testObserveValueForKeyPathWithIdentifier { change:@{NSKeyValueChangeOldKey : returnedObject} context:nil]; OCMVerify([mockFlutterAPI - observeValueForObjectWithIdentifier:@0 + observeValueForObjectWithIdentifier:0 keyPath:@"keyPath" - objectIdentifier:@1 + objectIdentifier:1 changeKeys:[OCMArg checkWithBlock:^BOOL( NSArray *value) { @@ -175,7 +174,7 @@ - (void)testObserveValueForKeyPathWithIdentifier { }] changeValues:[OCMArg checkWithBlock:^BOOL(id value) { FWFObjectOrIdentifier *object = (FWFObjectOrIdentifier *)value[0]; - return object.isIdentifier.boolValue && [@(2) isEqual:object.value]; + return object.isIdentifier && [@(2) isEqual:object.value]; }] completion:OCMOCK_ANY]); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFPreferencesHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFPreferencesHostApiTests.m index 95b81ad5c389..dcbe75bf58fb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFPreferencesHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFPreferencesHostApiTests.m @@ -20,7 +20,7 @@ - (void)testCreateFromWebViewConfigurationWithIdentifier { [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createFromWebViewConfigurationWithIdentifier:1 configurationIdentifier:0 error:&error]; WKPreferences *preferences = (WKPreferences *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([preferences isKindOfClass:[WKPreferences class]]); XCTAssertNil(error); @@ -36,7 +36,7 @@ - (void)testSetJavaScriptEnabled { [[FWFPreferencesHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI setJavaScriptEnabledForPreferencesWithIdentifier:@0 isEnabled:@YES error:&error]; + [hostAPI setJavaScriptEnabledForPreferencesWithIdentifier:0 isEnabled:YES error:&error]; OCMVerify([mockPreferences setJavaScriptEnabled:YES]); XCTAssertNil(error); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScriptMessageHandlerHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScriptMessageHandlerHostApiTests.m index 84d31d1c543e..fb9958726286 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScriptMessageHandlerHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScriptMessageHandlerHostApiTests.m @@ -50,7 +50,7 @@ - (void)testCreateWithIdentifier { instanceManager:instanceManager]; FlutterError *error; - [hostAPI createWithIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:0 error:&error]; FWFScriptMessageHandler *scriptMessageHandler = (FWFScriptMessageHandler *)[instanceManager instanceForIdentifier:0]; @@ -78,8 +78,8 @@ - (void)testDidReceiveScriptMessageForHandler { [mockHandler userContentController:userContentController didReceiveScriptMessage:mockScriptMessage]; OCMVerify([mockFlutterAPI - didReceiveScriptMessageForHandlerWithIdentifier:@0 - userContentControllerIdentifier:@1 + didReceiveScriptMessageForHandlerWithIdentifier:0 + userContentControllerIdentifier:1 message:[OCMArg isKindOfClass:[FWFWKScriptMessageData class]] completion:OCMOCK_ANY]); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScrollViewHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScrollViewHostApiTests.m index ede8dcf35d89..b44a080ee47a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScrollViewHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScrollViewHostApiTests.m @@ -24,7 +24,7 @@ - (void)testGetContentOffset { FlutterError *error; NSArray *expectedValue = @[ @1.0, @2.0 ]; - XCTAssertEqualObjects([hostAPI contentOffsetForScrollViewWithIdentifier:@0 error:&error], + XCTAssertEqualObjects([hostAPI contentOffsetForScrollViewWithIdentifier:0 error:&error], expectedValue); XCTAssertNil(error); } @@ -40,7 +40,7 @@ - (void)testScrollBy { [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI scrollByForScrollViewWithIdentifier:@0 x:@1 y:@2 error:&error]; + [hostAPI scrollByForScrollViewWithIdentifier:0 x:1 y:2 error:&error]; XCTAssertEqual(scrollView.contentOffset.x, 2); XCTAssertEqual(scrollView.contentOffset.y, 4); XCTAssertNil(error); @@ -56,7 +56,7 @@ - (void)testSetContentOffset { [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI setContentOffsetForScrollViewWithIdentifier:@0 toX:@1 y:@2 error:&error]; + [hostAPI setContentOffsetForScrollViewWithIdentifier:0 toX:1 y:2 error:&error]; XCTAssertEqual(scrollView.contentOffset.x, 1); XCTAssertEqual(scrollView.contentOffset.y, 2); XCTAssertNil(error); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIDelegateHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIDelegateHostApiTests.m index 72366762b7f5..ac13eb5811be 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIDelegateHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIDelegateHostApiTests.m @@ -5,6 +5,7 @@ @import Flutter; @import XCTest; @import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; #import @@ -50,7 +51,7 @@ - (void)testCreateWithIdentifier { instanceManager:instanceManager]; FlutterError *error; - [hostAPI createWithIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:0 error:&error]; FWFUIDelegate *delegate = (FWFUIDelegate *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([delegate conformsToProtocol:@protocol(WKUIDelegate)]); @@ -70,12 +71,8 @@ - (void)testOnCreateWebViewForDelegateWithIdentifier { WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; id mockConfigurationFlutterApi = OCMPartialMock(mockFlutterAPI.webViewConfigurationFlutterApi); - NSNumber *__block configurationIdentifier; - OCMStub([mockConfigurationFlutterApi createWithIdentifier:[OCMArg checkWithBlock:^BOOL(id value) { - configurationIdentifier = value; - return YES; - }] - completion:OCMOCK_ANY]); + OCMStub([mockConfigurationFlutterApi createWithIdentifier:0 completion:OCMOCK_ANY]) + .ignoringNonObjectArgs(); WKNavigationAction *mockNavigationAction = OCMClassMock([WKNavigationAction class]); OCMStub([mockNavigationAction request]) @@ -85,13 +82,16 @@ - (void)testOnCreateWebViewForDelegateWithIdentifier { OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); OCMStub([mockNavigationAction targetFrame]).andReturn(mockFrameInfo); + // Creating the webview will create a configuration on the host side, using the next available + // identifier, so save that for checking against later. + NSInteger configurationIdentifier = instanceManager.nextIdentifier; [mockDelegate webView:mockWebView createWebViewWithConfiguration:configuration forNavigationAction:mockNavigationAction windowFeatures:OCMClassMock([WKWindowFeatures class])]; OCMVerify([mockFlutterAPI - onCreateWebViewForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + onCreateWebViewForDelegateWithIdentifier:0 + webViewIdentifier:1 configurationIdentifier:configurationIdentifier navigationAction:[OCMArg isKindOfClass:[FWFWKNavigationActionData class]] @@ -125,8 +125,8 @@ - (void)testRequestMediaCapturePermissionForOrigin API_AVAILABLE(ios(15.0)) { }]; OCMVerify([mockFlutterAPI - requestMediaCapturePermissionForDelegateWithIdentifier:@0 - webViewIdentifier:@1 + requestMediaCapturePermissionForDelegateWithIdentifier:0 + webViewIdentifier:1 origin:[OCMArg isKindOfClass: [FWFWKSecurityOriginData class]] diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIViewHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIViewHostApiTests.m index 65a24d97a39a..d15937b1fe9c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIViewHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIViewHostApiTests.m @@ -22,7 +22,7 @@ - (void)testSetBackgroundColor { [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI setBackgroundColorForViewWithIdentifier:@0 toValue:@123 error:&error]; + [hostAPI setBackgroundColorForViewWithIdentifier:0 toValue:@123 error:&error]; OCMVerify([mockUIView setBackgroundColor:[UIColor colorWithRed:(123 >> 16 & 0xff) / 255.0 green:(123 >> 8 & 0xff) / 255.0 @@ -41,7 +41,7 @@ - (void)testSetOpaque { [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI setOpaqueForViewWithIdentifier:@0 isOpaque:@YES error:&error]; + [hostAPI setOpaqueForViewWithIdentifier:0 isOpaque:YES error:&error]; OCMVerify([mockUIView setOpaque:YES]); XCTAssertNil(error); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFURLTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFURLTests.m index bf4dc36048f9..7e8d2ad0f129 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFURLTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFURLTests.m @@ -24,7 +24,7 @@ - (void)testAbsoluteString { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostApi absoluteStringForNSURLWithIdentifier:@(0) error:&error], + XCTAssertEqualObjects([hostApi absoluteStringForNSURLWithIdentifier:0 error:&error], @"https://www.google.com"); XCTAssertNil(error); } @@ -43,6 +43,6 @@ - (void)testFlutterApiCreate { }]; long identifier = [instanceManager identifierWithStrongReferenceForInstance:url]; - OCMVerify([flutterApi.api createWithIdentifier:@(identifier) completion:OCMOCK_ANY]); + OCMVerify([flutterApi.api createWithIdentifier:identifier completion:OCMOCK_ANY]); } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUserContentControllerHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUserContentControllerHostApiTests.m index 4f523e6da402..38da70fd62a3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUserContentControllerHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUserContentControllerHostApiTests.m @@ -20,7 +20,7 @@ - (void)testCreateFromWebViewConfigurationWithIdentifier { [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createFromWebViewConfigurationWithIdentifier:1 configurationIdentifier:0 error:&error]; WKUserContentController *userContentController = (WKUserContentController *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([userContentController isKindOfClass:[WKUserContentController class]]); @@ -42,8 +42,8 @@ - (void)testAddScriptMessageHandler { [instanceManager addDartCreatedInstance:mockMessageHandler withIdentifier:1]; FlutterError *error; - [hostAPI addScriptMessageHandlerForControllerWithIdentifier:@0 - handlerIdentifier:@1 + [hostAPI addScriptMessageHandlerForControllerWithIdentifier:0 + handlerIdentifier:1 ofName:@"apple" error:&error]; OCMVerify([mockUserContentController addScriptMessageHandler:mockMessageHandler name:@"apple"]); @@ -61,7 +61,7 @@ - (void)testRemoveScriptMessageHandler { [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI removeScriptMessageHandlerForControllerWithIdentifier:@0 name:@"apple" error:&error]; + [hostAPI removeScriptMessageHandlerForControllerWithIdentifier:0 name:@"apple" error:&error]; OCMVerify([mockUserContentController removeScriptMessageHandlerForName:@"apple"]); XCTAssertNil(error); } @@ -77,7 +77,7 @@ - (void)testRemoveAllScriptMessageHandlers API_AVAILABLE(ios(14.0)) { [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI removeAllScriptMessageHandlersForControllerWithIdentifier:@0 error:&error]; + [hostAPI removeAllScriptMessageHandlersForControllerWithIdentifier:0 error:&error]; OCMVerify([mockUserContentController removeAllScriptMessageHandlers]); XCTAssertNil(error); } @@ -94,7 +94,7 @@ - (void)testAddUserScript { FlutterError *error; [hostAPI - addUserScriptForControllerWithIdentifier:@0 + addUserScriptForControllerWithIdentifier:0 userScript: [FWFWKUserScriptData makeWithSource:@"runAScript" @@ -102,7 +102,7 @@ - (void)testAddUserScript { [FWFWKUserScriptInjectionTimeEnumData makeWithValue: FWFWKUserScriptInjectionTimeEnumAtDocumentEnd] - isMainFrameOnly:@YES] + isMainFrameOnly:YES] error:&error]; OCMVerify([mockUserContentController addUserScript:[OCMArg isKindOfClass:[WKUserScript class]]]); @@ -120,7 +120,7 @@ - (void)testRemoveAllUserScripts { [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI removeAllUserScriptsForControllerWithIdentifier:@0 error:&error]; + [hostAPI removeAllUserScriptsForControllerWithIdentifier:0 error:&error]; OCMVerify([mockUserContentController removeAllUserScripts]); XCTAssertNil(error); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewConfigurationHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewConfigurationHostApiTests.m index 98be6dfe9e2b..5670468c0439 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewConfigurationHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewConfigurationHostApiTests.m @@ -19,7 +19,7 @@ - (void)testCreateWithIdentifier { instanceManager:instanceManager]; FlutterError *error; - [hostAPI createWithIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:0 error:&error]; WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([configuration isKindOfClass:[WKWebViewConfiguration class]]); @@ -37,7 +37,7 @@ - (void)testCreateFromWebViewWithIdentifier { [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FlutterError *error; - [hostAPI createFromWebViewWithIdentifier:@1 webViewIdentifier:@0 error:&error]; + [hostAPI createFromWebViewWithIdentifier:1 webViewIdentifier:0 error:&error]; WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([configuration isKindOfClass:[WKWebViewConfiguration class]]); @@ -55,9 +55,7 @@ - (void)testSetAllowsInlineMediaPlayback { instanceManager:instanceManager]; FlutterError *error; - [hostAPI setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:@0 - isAllowed:@NO - error:&error]; + [hostAPI setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:0 isAllowed:NO error:&error]; OCMVerify([mockWebViewConfiguration setAllowsInlineMediaPlayback:NO]); XCTAssertNil(error); } @@ -73,8 +71,8 @@ - (void)testSetLimitsNavigationsToAppBoundDomains API_AVAILABLE(ios(14.0)) { instanceManager:instanceManager]; FlutterError *error; - [hostAPI setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:@0 - isLimited:@NO + [hostAPI setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:0 + isLimited:NO error:&error]; OCMVerify([mockWebViewConfiguration setLimitsNavigationsToAppBoundDomains:NO]); XCTAssertNil(error); @@ -92,7 +90,7 @@ - (void)testSetMediaTypesRequiringUserActionForPlayback { FlutterError *error; [hostAPI - setMediaTypesRequiresUserActionForConfigurationWithIdentifier:@0 + setMediaTypesRequiresUserActionForConfigurationWithIdentifier:0 forTypes:@[ [FWFWKAudiovisualMediaTypeEnumData makeWithValue: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m index 617f7309cdb5..568e5fe1ed1c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m @@ -23,7 +23,7 @@ - (void)testCreateWithIdentifier { [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:1 configurationIdentifier:0 error:&error]; WKWebView *webView = (WKWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([webView isKindOfClass:[WKWebView class]]); XCTAssertNil(error); @@ -44,7 +44,7 @@ - (void)testLoadRequest { httpMethod:@"get" httpBody:nil allHttpHeaderFields:@{@"a" : @"header"}]; - [hostAPI loadRequestForWebViewWithIdentifier:@0 request:requestData error:&error]; + [hostAPI loadRequestForWebViewWithIdentifier:0 request:requestData error:&error]; NSURL *url = [NSURL URLWithString:@"https://www.flutter.dev"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; @@ -70,7 +70,7 @@ - (void)testLoadRequestWithInvalidUrl { httpMethod:nil httpBody:nil allHttpHeaderFields:@{}]; - [hostAPI loadRequestForWebViewWithIdentifier:@0 request:requestData error:&error]; + [hostAPI loadRequestForWebViewWithIdentifier:0 request:requestData error:&error]; // When linking against the iOS 17 SDK or later, NSURL uses a lenient parser, and won't // fail to parse URLs, so the test must allow for either outcome. if (error) { @@ -95,7 +95,7 @@ - (void)testSetCustomUserAgent { instanceManager:instanceManager]; FlutterError *error; - [hostAPI setCustomUserAgentForWebViewWithIdentifier:@0 userAgent:@"userA" error:&error]; + [hostAPI setCustomUserAgentForWebViewWithIdentifier:0 userAgent:@"userA" error:&error]; OCMVerify([mockWebView setCustomUserAgent:@"userA"]); XCTAssertNil(error); } @@ -112,7 +112,7 @@ - (void)testURL { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI URLForWebViewWithIdentifier:@0 error:&error], + XCTAssertEqualObjects([hostAPI URLForWebViewWithIdentifier:0 error:&error], @"https://www.flutter.dev/"); XCTAssertNil(error); } @@ -129,7 +129,7 @@ - (void)testCanGoBack { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI canGoBackForWebViewWithIdentifier:@0 error:&error], @YES); + XCTAssertEqualObjects([hostAPI canGoBackForWebViewWithIdentifier:0 error:&error], @YES); XCTAssertNil(error); } @@ -147,7 +147,7 @@ - (void)testSetUIDelegate { [instanceManager addDartCreatedInstance:mockDelegate withIdentifier:1]; FlutterError *error; - [hostAPI setUIDelegateForWebViewWithIdentifier:@0 delegateIdentifier:@1 error:&error]; + [hostAPI setUIDelegateForWebViewWithIdentifier:0 delegateIdentifier:@1 error:&error]; OCMVerify([mockWebView setUIDelegate:mockDelegate]); XCTAssertNil(error); } @@ -166,7 +166,7 @@ - (void)testSetNavigationDelegate { [instanceManager addDartCreatedInstance:mockDelegate withIdentifier:1]; FlutterError *error; - [hostAPI setNavigationDelegateForWebViewWithIdentifier:@0 delegateIdentifier:@1 error:&error]; + [hostAPI setNavigationDelegateForWebViewWithIdentifier:0 delegateIdentifier:@1 error:&error]; OCMVerify([mockWebView setNavigationDelegate:mockDelegate]); XCTAssertNil(error); } @@ -183,7 +183,7 @@ - (void)testEstimatedProgress { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI estimatedProgressForWebViewWithIdentifier:@0 error:&error], @34.0); + XCTAssertEqualObjects([hostAPI estimatedProgressForWebViewWithIdentifier:0 error:&error], @34.0); XCTAssertNil(error); } @@ -198,7 +198,7 @@ - (void)testloadHTMLString { instanceManager:instanceManager]; FlutterError *error; - [hostAPI loadHTMLForWebViewWithIdentifier:@0 + [hostAPI loadHTMLForWebViewWithIdentifier:0 HTMLString:@"myString" baseURL:@"myBaseUrl" error:&error]; @@ -217,7 +217,7 @@ - (void)testLoadFileURL { instanceManager:instanceManager]; FlutterError *error; - [hostAPI loadFileForWebViewWithIdentifier:@0 + [hostAPI loadFileForWebViewWithIdentifier:0 fileURL:@"myFolder/apple.txt" readAccessURL:@"myFolder" error:&error]; @@ -249,7 +249,7 @@ - (void)testLoadFlutterAsset { assetManager:mockAssetManager]; FlutterError *error; - [hostAPI loadAssetForWebViewWithIdentifier:@0 assetKey:@"assets/index.html" error:&error]; + [hostAPI loadAssetForWebViewWithIdentifier:0 assetKey:@"assets/index.html" error:&error]; XCTAssertNil(error); OCMVerify([mockWebView @@ -269,7 +269,7 @@ - (void)testCanGoForward { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI canGoForwardForWebViewWithIdentifier:@0 error:&error], @NO); + XCTAssertEqualObjects([hostAPI canGoForwardForWebViewWithIdentifier:0 error:&error], @NO); XCTAssertNil(error); } @@ -284,7 +284,7 @@ - (void)testGoBack { instanceManager:instanceManager]; FlutterError *error; - [hostAPI goBackForWebViewWithIdentifier:@0 error:&error]; + [hostAPI goBackForWebViewWithIdentifier:0 error:&error]; OCMVerify([mockWebView goBack]); XCTAssertNil(error); } @@ -300,7 +300,7 @@ - (void)testGoForward { instanceManager:instanceManager]; FlutterError *error; - [hostAPI goForwardForWebViewWithIdentifier:@0 error:&error]; + [hostAPI goForwardForWebViewWithIdentifier:0 error:&error]; OCMVerify([mockWebView goForward]); XCTAssertNil(error); } @@ -316,7 +316,7 @@ - (void)testReload { instanceManager:instanceManager]; FlutterError *error; - [hostAPI reloadWebViewWithIdentifier:@0 error:&error]; + [hostAPI reloadWebViewWithIdentifier:0 error:&error]; OCMVerify([mockWebView reload]); XCTAssertNil(error); } @@ -333,7 +333,7 @@ - (void)testTitle { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI titleForWebViewWithIdentifier:@0 error:&error], @"myTitle"); + XCTAssertEqualObjects([hostAPI titleForWebViewWithIdentifier:0 error:&error], @"myTitle"); XCTAssertNil(error); } @@ -348,7 +348,7 @@ - (void)testSetAllowsBackForwardNavigationGestures { instanceManager:instanceManager]; FlutterError *error; - [hostAPI setAllowsBackForwardForWebViewWithIdentifier:@0 isAllowed:@YES error:&error]; + [hostAPI setAllowsBackForwardForWebViewWithIdentifier:0 isAllowed:YES error:&error]; OCMVerify([mockWebView setAllowsBackForwardNavigationGestures:YES]); XCTAssertNil(error); } @@ -369,7 +369,7 @@ - (void)testEvaluateJavaScript { NSString __block *returnValue; FlutterError __block *returnError; - [hostAPI evaluateJavaScriptForWebViewWithIdentifier:@0 + [hostAPI evaluateJavaScriptForWebViewWithIdentifier:0 javaScriptString:@"runJavaScript" completion:^(id result, FlutterError *error) { returnValue = result; @@ -403,7 +403,7 @@ - (void)testEvaluateJavaScriptReturnsNSErrorData { NSString __block *returnValue; FlutterError __block *returnError; - [hostAPI evaluateJavaScriptForWebViewWithIdentifier:@0 + [hostAPI evaluateJavaScriptForWebViewWithIdentifier:0 javaScriptString:@"runJavaScript" completion:^(id result, FlutterError *error) { returnValue = result; @@ -413,7 +413,7 @@ - (void)testEvaluateJavaScriptReturnsNSErrorData { XCTAssertNil(returnValue); FWFNSErrorData *errorData = returnError.details; XCTAssertTrue([errorData isKindOfClass:[FWFNSErrorData class]]); - XCTAssertEqualObjects(errorData.code, @0); + XCTAssertEqual(errorData.code, 0); XCTAssertEqualObjects(errorData.domain, @"errorDomain"); XCTAssertEqualObjects(errorData.userInfo, @{NSLocalizedDescriptionKey : @"description"}); } @@ -427,7 +427,7 @@ - (void)testWebViewContentInsetBehaviorShouldBeNever { [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:1 configurationIdentifier:0 error:&error]; FWFWebView *webView = (FWFWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertEqual(webView.scrollView.contentInsetAdjustmentBehavior, @@ -444,7 +444,7 @@ - (void)testScrollViewsAutomaticallyAdjustsScrollIndicatorInsetsShouldbeNoOnIOS1 [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createWithIdentifier:1 configurationIdentifier:0 error:&error]; FWFWebView *webView = (FWFWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertFalse(webView.scrollView.automaticallyAdjustsScrollIndicatorInsets); @@ -483,7 +483,7 @@ - (void)testSetInspectable API_AVAILABLE(ios(16.4), macos(13.3)) { instanceManager:instanceManager]; FlutterError *error; - [hostAPI setInspectableForWebViewWithIdentifier:@0 inspectable:@YES error:&error]; + [hostAPI setInspectableForWebViewWithIdentifier:0 inspectable:YES error:&error]; OCMVerify([mockWebView setInspectable:YES]); XCTAssertNil(error); } @@ -502,7 +502,7 @@ - (void)testCustomUserAgent { instanceManager:instanceManager]; FlutterError *error; - XCTAssertEqualObjects([hostAPI customUserAgentForWebViewWithIdentifier:@0 error:&error], + XCTAssertEqualObjects([hostAPI customUserAgentForWebViewWithIdentifier:0 error:&error], userAgent); XCTAssertNil(error); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebsiteDataStoreHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebsiteDataStoreHostApiTests.m index c518f55194c4..880c81e59e95 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebsiteDataStoreHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebsiteDataStoreHostApiTests.m @@ -20,7 +20,7 @@ - (void)testCreateFromWebViewConfigurationWithIdentifier { [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; - [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; + [hostAPI createFromWebViewConfigurationWithIdentifier:1 configurationIdentifier:0 error:&error]; WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([dataStore isKindOfClass:[WKWebsiteDataStore class]]); XCTAssertNil(error); @@ -32,7 +32,7 @@ - (void)testCreateDefaultDataStoreWithIdentifier { [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; - [hostAPI createDefaultDataStoreWithIdentifier:@0 error:&error]; + [hostAPI createDefaultDataStoreWithIdentifier:0 error:&error]; WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[instanceManager instanceForIdentifier:0]; XCTAssertEqualObjects(dataStore, [WKWebsiteDataStore defaultDataStore]); XCTAssertNil(error); @@ -59,12 +59,12 @@ - (void)testRemoveDataOfTypes { NSNumber __block *returnValue; FlutterError *__block blockError; - [hostAPI removeDataFromDataStoreWithIdentifier:@0 + [hostAPI removeDataFromDataStoreWithIdentifier:0 ofTypes:@[ [FWFWKWebsiteDataTypeEnumData makeWithValue:FWFWKWebsiteDataTypeEnumLocalStorage] ] - modifiedSince:@45.0 + modifiedSince:45.0 completion:^(NSNumber *result, FlutterError *error) { returnValue = result; blockError = error; 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 4180650574db..8f3dfa8aeba3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -211,7 +211,7 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - if (context.mounted) { + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m index 2f919839aa5b..c3d6699e41da 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m @@ -60,48 +60,48 @@ + (void)registerWithRegistrar:(NSObject *)registrar { instanceManager:[[FWFInstanceManager alloc] init]]; dispatch_async(dispatch_get_main_queue(), ^{ - [objectApi disposeObjectWithIdentifier:@(identifier) + [objectApi disposeObjectWithIdentifier:identifier completion:^(FlutterError *error) { NSAssert(!error, @"%@", error); }]; }); }]; - FWFWKHttpCookieStoreHostApiSetup( + SetUpFWFWKHttpCookieStoreHostApi( registrar.messenger, [[FWFHTTPCookieStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKNavigationDelegateHostApiSetup( + SetUpFWFWKNavigationDelegateHostApi( registrar.messenger, [[FWFNavigationDelegateHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); - FWFNSObjectHostApiSetup(registrar.messenger, + SetUpFWFNSObjectHostApi(registrar.messenger, [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKPreferencesHostApiSetup(registrar.messenger, [[FWFPreferencesHostApiImpl alloc] + SetUpFWFWKPreferencesHostApi(registrar.messenger, [[FWFPreferencesHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKScriptMessageHandlerHostApiSetup( + SetUpFWFWKScriptMessageHandlerHostApi( registrar.messenger, [[FWFScriptMessageHandlerHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); - FWFUIScrollViewHostApiSetup(registrar.messenger, [[FWFScrollViewHostApiImpl alloc] + SetUpFWFUIScrollViewHostApi(registrar.messenger, [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKUIDelegateHostApiSetup(registrar.messenger, [[FWFUIDelegateHostApiImpl alloc] + SetUpFWFWKUIDelegateHostApi(registrar.messenger, [[FWFUIDelegateHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); - FWFUIViewHostApiSetup(registrar.messenger, + SetUpFWFUIViewHostApi(registrar.messenger, [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKUserContentControllerHostApiSetup( + SetUpFWFWKUserContentControllerHostApi( registrar.messenger, [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKWebsiteDataStoreHostApiSetup( + SetUpFWFWKWebsiteDataStoreHostApi( registrar.messenger, [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]); - FWFWKWebViewConfigurationHostApiSetup( + SetUpFWFWKWebViewConfigurationHostApi( registrar.messenger, [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); - FWFWKWebViewHostApiSetup(registrar.messenger, [[FWFWebViewHostApiImpl alloc] + SetUpFWFWKWebViewHostApi(registrar.messenger, [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); - FWFNSUrlHostApiSetup(registrar.messenger, + SetUpFWFNSUrlHostApi(registrar.messenger, [[FWFURLHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); 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 28c029888a3a..3cbcea844a12 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m @@ -103,7 +103,7 @@ NSHTTPCookiePropertyKey _Nullable FWFNativeNSHTTPCookiePropertyKeyFromEnumData( return [[WKUserScript alloc] initWithSource:data.source injectionTime:FWFNativeWKUserScriptInjectionTimeFromEnumData(data.injectionTime) - forMainFrameOnly:data.isMainFrameOnly.boolValue]; + forMainFrameOnly:data.isMainFrameOnly]; } WKUserScriptInjectionTime FWFNativeWKUserScriptInjectionTimeFromEnumData( @@ -176,7 +176,7 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData( } FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info) { - return [FWFWKFrameInfoData makeWithIsMainFrame:@(info.isMainFrame)]; + return [FWFWKFrameInfoData makeWithIsMainFrame:info.isMainFrame]; } WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( @@ -204,7 +204,7 @@ WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( } } } - return [FWFNSErrorData makeWithCode:@(error.code) domain:error.domain userInfo:userInfo]; + return [FWFNSErrorData makeWithCode:error.code domain:error.domain userInfo:userInfo]; } FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey( @@ -253,7 +253,7 @@ FWFWKNavigationType FWFWKNavigationTypeFromNativeWKNavigationType(WKNavigationTy FWFWKSecurityOriginData *FWFWKSecurityOriginDataFromNativeWKSecurityOrigin( WKSecurityOrigin *origin) { return [FWFWKSecurityOriginData makeWithHost:origin.host - port:@(origin.port) + port:origin.port protocol:origin.protocol]; } 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 11c49d24b541..8469438ab020 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 (v10.1.4), do not edit directly. +// Autogenerated from Pigeon (v13.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -24,6 +24,12 @@ typedef NS_ENUM(NSUInteger, FWFNSKeyValueObservingOptionsEnum) { FWFNSKeyValueObservingOptionsEnumPriorNotification = 3, }; +/// Wrapper for FWFNSKeyValueObservingOptionsEnum to allow for nullability. +@interface FWFNSKeyValueObservingOptionsEnumBox : NSObject +@property(nonatomic, assign) FWFNSKeyValueObservingOptionsEnum value; +- (instancetype)initWithValue:(FWFNSKeyValueObservingOptionsEnum)value; +@end + /// Mirror of NSKeyValueChange. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc. @@ -34,6 +40,12 @@ typedef NS_ENUM(NSUInteger, FWFNSKeyValueChangeEnum) { FWFNSKeyValueChangeEnumReplacement = 3, }; +/// Wrapper for FWFNSKeyValueChangeEnum to allow for nullability. +@interface FWFNSKeyValueChangeEnumBox : NSObject +@property(nonatomic, assign) FWFNSKeyValueChangeEnum value; +- (instancetype)initWithValue:(FWFNSKeyValueChangeEnum)value; +@end + /// Mirror of NSKeyValueChangeKey. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc. @@ -46,6 +58,12 @@ typedef NS_ENUM(NSUInteger, FWFNSKeyValueChangeKeyEnum) { FWFNSKeyValueChangeKeyEnumUnknown = 5, }; +/// Wrapper for FWFNSKeyValueChangeKeyEnum to allow for nullability. +@interface FWFNSKeyValueChangeKeyEnumBox : NSObject +@property(nonatomic, assign) FWFNSKeyValueChangeKeyEnum value; +- (instancetype)initWithValue:(FWFNSKeyValueChangeKeyEnum)value; +@end + /// Mirror of WKUserScriptInjectionTime. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc. @@ -54,6 +72,12 @@ typedef NS_ENUM(NSUInteger, FWFWKUserScriptInjectionTimeEnum) { FWFWKUserScriptInjectionTimeEnumAtDocumentEnd = 1, }; +/// Wrapper for FWFWKUserScriptInjectionTimeEnum to allow for nullability. +@interface FWFWKUserScriptInjectionTimeEnumBox : NSObject +@property(nonatomic, assign) FWFWKUserScriptInjectionTimeEnum value; +- (instancetype)initWithValue:(FWFWKUserScriptInjectionTimeEnum)value; +@end + /// Mirror of WKAudiovisualMediaTypes. /// /// See @@ -65,6 +89,12 @@ typedef NS_ENUM(NSUInteger, FWFWKAudiovisualMediaTypeEnum) { FWFWKAudiovisualMediaTypeEnumAll = 3, }; +/// Wrapper for FWFWKAudiovisualMediaTypeEnum to allow for nullability. +@interface FWFWKAudiovisualMediaTypeEnumBox : NSObject +@property(nonatomic, assign) FWFWKAudiovisualMediaTypeEnum value; +- (instancetype)initWithValue:(FWFWKAudiovisualMediaTypeEnum)value; +@end + /// Mirror of WKWebsiteDataTypes. /// /// See @@ -80,6 +110,12 @@ typedef NS_ENUM(NSUInteger, FWFWKWebsiteDataTypeEnum) { FWFWKWebsiteDataTypeEnumIndexedDBDatabases = 7, }; +/// Wrapper for FWFWKWebsiteDataTypeEnum to allow for nullability. +@interface FWFWKWebsiteDataTypeEnumBox : NSObject +@property(nonatomic, assign) FWFWKWebsiteDataTypeEnum value; +- (instancetype)initWithValue:(FWFWKWebsiteDataTypeEnum)value; +@end + /// Mirror of WKNavigationActionPolicy. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. @@ -88,6 +124,12 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationActionPolicyEnum) { FWFWKNavigationActionPolicyEnumCancel = 1, }; +/// Wrapper for FWFWKNavigationActionPolicyEnum to allow for nullability. +@interface FWFWKNavigationActionPolicyEnumBox : NSObject +@property(nonatomic, assign) FWFWKNavigationActionPolicyEnum value; +- (instancetype)initWithValue:(FWFWKNavigationActionPolicyEnum)value; +@end + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -108,6 +150,12 @@ typedef NS_ENUM(NSUInteger, FWFNSHttpCookiePropertyKeyEnum) { FWFNSHttpCookiePropertyKeyEnumVersion = 13, }; +/// Wrapper for FWFNSHttpCookiePropertyKeyEnum to allow for nullability. +@interface FWFNSHttpCookiePropertyKeyEnumBox : NSObject +@property(nonatomic, assign) FWFNSHttpCookiePropertyKeyEnum value; +- (instancetype)initWithValue:(FWFNSHttpCookiePropertyKeyEnum)value; +@end + /// An object that contains information about an action that causes navigation /// to occur. /// @@ -151,6 +199,12 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { FWFWKNavigationTypeUnknown = 6, }; +/// Wrapper for FWFWKNavigationType to allow for nullability. +@interface FWFWKNavigationTypeBox : NSObject +@property(nonatomic, assign) FWFWKNavigationType value; +- (instancetype)initWithValue:(FWFWKNavigationType)value; +@end + /// Possible permission decisions for device resource access. /// /// See https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc. @@ -172,6 +226,12 @@ typedef NS_ENUM(NSUInteger, FWFWKPermissionDecision) { FWFWKPermissionDecisionPrompt = 2, }; +/// Wrapper for FWFWKPermissionDecision to allow for nullability. +@interface FWFWKPermissionDecisionBox : NSObject +@property(nonatomic, assign) FWFWKPermissionDecision value; +- (instancetype)initWithValue:(FWFWKPermissionDecision)value; +@end + /// List of the types of media devices that can capture audio, video, or both. /// /// See https://developer.apple.com/documentation/webkit/wkmediacapturetype?language=objc. @@ -198,6 +258,12 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { FWFWKMediaCaptureTypeUnknown = 3, }; +/// Wrapper for FWFWKMediaCaptureType to allow for nullability. +@interface FWFWKMediaCaptureTypeBox : NSObject +@property(nonatomic, assign) FWFWKMediaCaptureType value; +- (instancetype)initWithValue:(FWFWKMediaCaptureType)value; +@end + @class FWFNSKeyValueObservingOptionsEnumData; @class FWFNSKeyValueChangeKeyEnumData; @class FWFWKUserScriptInjectionTimeEnumData; @@ -293,7 +359,7 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @property(nonatomic, copy) NSString *url; @property(nonatomic, copy, nullable) NSString *httpMethod; @property(nonatomic, strong, nullable) FlutterStandardTypedData *httpBody; -@property(nonatomic, strong) NSDictionary *allHttpHeaderFields; +@property(nonatomic, copy) NSDictionary *allHttpHeaderFields; @end /// Mirror of WKUserScript. @@ -304,10 +370,10 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime - isMainFrameOnly:(NSNumber *)isMainFrameOnly; + isMainFrameOnly:(BOOL)isMainFrameOnly; @property(nonatomic, copy) NSString *source; @property(nonatomic, strong, nullable) FWFWKUserScriptInjectionTimeEnumData *injectionTime; -@property(nonatomic, strong) NSNumber *isMainFrameOnly; +@property(nonatomic, assign) BOOL isMainFrameOnly; @end /// Mirror of WKNavigationAction. @@ -330,8 +396,8 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @interface FWFWKFrameInfoData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithIsMainFrame:(NSNumber *)isMainFrame; -@property(nonatomic, strong) NSNumber *isMainFrame; ++ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame; +@property(nonatomic, assign) BOOL isMainFrame; @end /// Mirror of NSError. @@ -340,12 +406,12 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @interface FWFNSErrorData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithCode:(NSNumber *)code ++ (instancetype)makeWithCode:(NSInteger)code domain:(NSString *)domain userInfo:(nullable NSDictionary *)userInfo; -@property(nonatomic, strong) NSNumber *code; +@property(nonatomic, assign) NSInteger code; @property(nonatomic, copy) NSString *domain; -@property(nonatomic, strong, nullable) NSDictionary *userInfo; +@property(nonatomic, copy, nullable) NSDictionary *userInfo; @end /// Mirror of WKScriptMessage. @@ -354,9 +420,9 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @interface FWFWKScriptMessageData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithName:(NSString *)name body:(id)body; ++ (instancetype)makeWithName:(NSString *)name body:(nullable id)body; @property(nonatomic, copy) NSString *name; -@property(nonatomic, strong) id body; +@property(nonatomic, strong, nullable) id body; @end /// Mirror of WKSecurityOrigin. @@ -365,9 +431,9 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @interface FWFWKSecurityOriginData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithHost:(NSString *)host port:(NSNumber *)port protocol:(NSString *)protocol; ++ (instancetype)makeWithHost:(NSString *)host port:(NSInteger)port protocol:(NSString *)protocol; @property(nonatomic, copy) NSString *host; -@property(nonatomic, strong) NSNumber *port; +@property(nonatomic, assign) NSInteger port; @property(nonatomic, copy) NSString *protocol; @end @@ -379,8 +445,8 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithPropertyKeys:(NSArray *)propertyKeys propertyValues:(NSArray *)propertyValues; -@property(nonatomic, strong) NSArray *propertyKeys; -@property(nonatomic, strong) NSArray *propertyValues; +@property(nonatomic, copy) NSArray *propertyKeys; +@property(nonatomic, copy) NSArray *propertyValues; @end /// An object that can represent either a value supported by @@ -389,11 +455,11 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) { @interface FWFObjectOrIdentifier : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithValue:(id)value isIdentifier:(NSNumber *)isIdentifier; -@property(nonatomic, strong) id value; ++ (instancetype)makeWithValue:(nullable id)value isIdentifier:(BOOL)isIdentifier; +@property(nonatomic, strong, nullable) id value; /// Whether value is an int that is used to retrieve an instance stored in an /// `InstanceManager`. -@property(nonatomic, strong) NSNumber *isIdentifier; +@property(nonatomic, assign) BOOL isIdentifier; @end /// The codec used by FWFWKWebsiteDataStoreHostApi. @@ -403,19 +469,19 @@ NSObject *FWFWKWebsiteDataStoreHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. @protocol FWFWKWebsiteDataStoreHostApi -- (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier - configurationIdentifier:(NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)createDefaultDataStoreWithIdentifier:(NSNumber *)identifier +- (void)createDefaultDataStoreWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)removeDataFromDataStoreWithIdentifier:(NSNumber *)identifier +- (void)removeDataFromDataStoreWithIdentifier:(NSInteger)identifier ofTypes:(NSArray *)dataTypes - modifiedSince:(NSNumber *)modificationTimeInSecondsSinceEpoch + modifiedSince:(double)modificationTimeInSecondsSinceEpoch completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @end -extern void FWFWKWebsiteDataStoreHostApiSetup( +extern void SetUpFWFWKWebsiteDataStoreHostApi( id binaryMessenger, NSObject *_Nullable api); @@ -426,15 +492,15 @@ NSObject *FWFUIViewHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/uikit/uiview?language=objc. @protocol FWFUIViewHostApi -- (void)setBackgroundColorForViewWithIdentifier:(NSNumber *)identifier +- (void)setBackgroundColorForViewWithIdentifier:(NSInteger)identifier toValue:(nullable NSNumber *)value error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setOpaqueForViewWithIdentifier:(NSNumber *)identifier - isOpaque:(NSNumber *)opaque +- (void)setOpaqueForViewWithIdentifier:(NSInteger)identifier + isOpaque:(BOOL)opaque error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFUIViewHostApiSetup(id binaryMessenger, +extern void SetUpFWFUIViewHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFUIScrollViewHostApi. @@ -444,24 +510,24 @@ NSObject *FWFUIScrollViewHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/uikit/uiscrollview?language=objc. @protocol FWFUIScrollViewHostApi -- (void)createFromWebViewWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)createFromWebViewWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable NSArray *) - contentOffsetForScrollViewWithIdentifier:(NSNumber *)identifier + contentOffsetForScrollViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)scrollByForScrollViewWithIdentifier:(NSNumber *)identifier - x:(NSNumber *)x - y:(NSNumber *)y +- (void)scrollByForScrollViewWithIdentifier:(NSInteger)identifier + x:(double)x + y:(double)y error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setContentOffsetForScrollViewWithIdentifier:(NSNumber *)identifier - toX:(NSNumber *)x - y:(NSNumber *)y +- (void)setContentOffsetForScrollViewWithIdentifier:(NSInteger)identifier + toX:(double)x + y:(double)y error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFUIScrollViewHostApiSetup(id binaryMessenger, +extern void SetUpFWFUIScrollViewHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKWebViewConfigurationHostApi. @@ -471,21 +537,21 @@ NSObject *FWFWKWebViewConfigurationHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @protocol FWFWKWebViewConfigurationHostApi -- (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)createFromWebViewWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; +- (void)createFromWebViewWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(NSNumber *)identifier - isAllowed:(NSNumber *)allow +- (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(NSInteger)identifier + isAllowed:(BOOL)allow error: (FlutterError *_Nullable *_Nonnull) error; -- (void)setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:(NSNumber *)identifier - isLimited:(NSNumber *)limit +- (void)setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:(NSInteger)identifier + isLimited:(BOOL)limit error:(FlutterError *_Nullable *_Nonnull)error; - (void) - setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(NSNumber *)identifier + setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(NSInteger)identifier forTypes: (NSArray< FWFWKAudiovisualMediaTypeEnumData @@ -495,7 +561,7 @@ NSObject *FWFWKWebViewConfigurationHostApiGetCodec(void); error; @end -extern void FWFWKWebViewConfigurationHostApiSetup( +extern void SetUpFWFWKWebViewConfigurationHostApi( id binaryMessenger, NSObject *_Nullable api); @@ -507,7 +573,7 @@ NSObject *FWFWKWebViewConfigurationFlutterApiGetCodec(void) /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @interface FWFWKWebViewConfigurationFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)createWithIdentifier:(NSNumber *)identifier +- (void)createWithIdentifier:(NSInteger)identifier completion:(void (^)(FlutterError *_Nullable))completion; @end @@ -518,29 +584,29 @@ NSObject *FWFWKUserContentControllerHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc. @protocol FWFWKUserContentControllerHostApi -- (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier - configurationIdentifier:(NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)addScriptMessageHandlerForControllerWithIdentifier:(NSNumber *)identifier - handlerIdentifier:(NSNumber *)handlerIdentifier +- (void)addScriptMessageHandlerForControllerWithIdentifier:(NSInteger)identifier + handlerIdentifier:(NSInteger)handlerIdentifier ofName:(NSString *)name error:(FlutterError *_Nullable *_Nonnull)error; -- (void)removeScriptMessageHandlerForControllerWithIdentifier:(NSNumber *)identifier +- (void)removeScriptMessageHandlerForControllerWithIdentifier:(NSInteger)identifier name:(NSString *)name error:(FlutterError *_Nullable *_Nonnull) error; -- (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(NSNumber *)identifier +- (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(NSInteger)identifier error: (FlutterError *_Nullable *_Nonnull) error; -- (void)addUserScriptForControllerWithIdentifier:(NSNumber *)identifier +- (void)addUserScriptForControllerWithIdentifier:(NSInteger)identifier userScript:(FWFWKUserScriptData *)userScript error:(FlutterError *_Nullable *_Nonnull)error; -- (void)removeAllUserScriptsForControllerWithIdentifier:(NSNumber *)identifier +- (void)removeAllUserScriptsForControllerWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFWKUserContentControllerHostApiSetup( +extern void SetUpFWFWKUserContentControllerHostApi( id binaryMessenger, NSObject *_Nullable api); @@ -551,15 +617,15 @@ NSObject *FWFWKPreferencesHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkpreferences?language=objc. @protocol FWFWKPreferencesHostApi -- (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier - configurationIdentifier:(NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setJavaScriptEnabledForPreferencesWithIdentifier:(NSNumber *)identifier - isEnabled:(NSNumber *)enabled +- (void)setJavaScriptEnabledForPreferencesWithIdentifier:(NSInteger)identifier + isEnabled:(BOOL)enabled error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFWKPreferencesHostApiSetup(id binaryMessenger, +extern void SetUpFWFWKPreferencesHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKScriptMessageHandlerHostApi. @@ -569,10 +635,10 @@ NSObject *FWFWKScriptMessageHandlerHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @protocol FWFWKScriptMessageHandlerHostApi -- (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFWKScriptMessageHandlerHostApiSetup( +extern void SetUpFWFWKScriptMessageHandlerHostApi( id binaryMessenger, NSObject *_Nullable api); @@ -584,8 +650,8 @@ NSObject *FWFWKScriptMessageHandlerFlutterApiGetCodec(void) /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @interface FWFWKScriptMessageHandlerFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)didReceiveScriptMessageForHandlerWithIdentifier:(NSNumber *)identifier - userContentControllerIdentifier:(NSNumber *)userContentControllerIdentifier +- (void)didReceiveScriptMessageForHandlerWithIdentifier:(NSInteger)identifier + userContentControllerIdentifier:(NSInteger)userContentControllerIdentifier message:(FWFWKScriptMessageData *)message completion: (void (^)(FlutterError *_Nullable))completion; @@ -598,10 +664,10 @@ NSObject *FWFWKNavigationDelegateHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @protocol FWFWKNavigationDelegateHostApi -- (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFWKNavigationDelegateHostApiSetup( +extern void SetUpFWFWKNavigationDelegateHostApi( id binaryMessenger, NSObject *_Nullable api); @@ -613,35 +679,35 @@ NSObject *FWFWKNavigationDelegateFlutterApiGetCodec(void); /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @interface FWFWKNavigationDelegateFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)didFinishNavigationForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier URL:(nullable NSString *)url completion:(void (^)(FlutterError *_Nullable))completion; -- (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier URL:(nullable NSString *)url completion:(void (^)(FlutterError *_Nullable)) completion; - (void) - decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier + decidePolicyForNavigationActionForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier navigationAction: (FWFWKNavigationActionData *)navigationAction completion: (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, FlutterError *_Nullable))completion; -- (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FWFNSErrorData *)error completion:(void (^)(FlutterError *_Nullable))completion; -- (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FWFNSErrorData *)error completion:(void (^)(FlutterError *_Nullable)) completion; -- (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier completion: (void (^)(FlutterError *_Nullable)) completion; @@ -654,21 +720,21 @@ NSObject *FWFNSObjectHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/objectivec/nsobject. @protocol FWFNSObjectHostApi -- (void)disposeObjectWithIdentifier:(NSNumber *)identifier +- (void)disposeObjectWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)addObserverForObjectWithIdentifier:(NSNumber *)identifier - observerIdentifier:(NSNumber *)observerIdentifier +- (void)addObserverForObjectWithIdentifier:(NSInteger)identifier + observerIdentifier:(NSInteger)observerIdentifier keyPath:(NSString *)keyPath options: (NSArray *)options error:(FlutterError *_Nullable *_Nonnull)error; -- (void)removeObserverForObjectWithIdentifier:(NSNumber *)identifier - observerIdentifier:(NSNumber *)observerIdentifier +- (void)removeObserverForObjectWithIdentifier:(NSInteger)identifier + observerIdentifier:(NSInteger)observerIdentifier keyPath:(NSString *)keyPath error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFNSObjectHostApiSetup(id binaryMessenger, +extern void SetUpFWFNSObjectHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFNSObjectFlutterApi. @@ -679,13 +745,13 @@ NSObject *FWFNSObjectFlutterApiGetCodec(void); /// See https://developer.apple.com/documentation/objectivec/nsobject. @interface FWFNSObjectFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)observeValueForObjectWithIdentifier:(NSNumber *)identifier +- (void)observeValueForObjectWithIdentifier:(NSInteger)identifier keyPath:(NSString *)keyPath - objectIdentifier:(NSNumber *)objectIdentifier + objectIdentifier:(NSInteger)objectIdentifier changeKeys:(NSArray *)changeKeys changeValues:(NSArray *)changeValues completion:(void (^)(FlutterError *_Nullable))completion; -- (void)disposeObjectWithIdentifier:(NSNumber *)identifier +- (void)disposeObjectWithIdentifier:(NSInteger)identifier completion:(void (^)(FlutterError *_Nullable))completion; @end @@ -696,70 +762,70 @@ NSObject *FWFWKWebViewHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkwebview?language=objc. @protocol FWFWKWebViewHostApi -- (void)createWithIdentifier:(NSNumber *)identifier - configurationIdentifier:(NSNumber *)configurationIdentifier +- (void)createWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setUIDelegateForWebViewWithIdentifier:(NSNumber *)identifier +- (void)setUIDelegateForWebViewWithIdentifier:(NSInteger)identifier delegateIdentifier:(nullable NSNumber *)uiDelegateIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setNavigationDelegateForWebViewWithIdentifier:(NSNumber *)identifier +- (void)setNavigationDelegateForWebViewWithIdentifier:(NSInteger)identifier delegateIdentifier: (nullable NSNumber *)navigationDelegateIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)URLForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSString *)URLForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)estimatedProgressForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSNumber *)estimatedProgressForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull) error; -- (void)loadRequestForWebViewWithIdentifier:(NSNumber *)identifier +- (void)loadRequestForWebViewWithIdentifier:(NSInteger)identifier request:(FWFNSUrlRequestData *)request error:(FlutterError *_Nullable *_Nonnull)error; -- (void)loadHTMLForWebViewWithIdentifier:(NSNumber *)identifier +- (void)loadHTMLForWebViewWithIdentifier:(NSInteger)identifier HTMLString:(NSString *)string baseURL:(nullable NSString *)baseUrl error:(FlutterError *_Nullable *_Nonnull)error; -- (void)loadFileForWebViewWithIdentifier:(NSNumber *)identifier +- (void)loadFileForWebViewWithIdentifier:(NSInteger)identifier fileURL:(NSString *)url readAccessURL:(NSString *)readAccessUrl error:(FlutterError *_Nullable *_Nonnull)error; -- (void)loadAssetForWebViewWithIdentifier:(NSNumber *)identifier +- (void)loadAssetForWebViewWithIdentifier:(NSInteger)identifier assetKey:(NSString *)key error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)canGoBackForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSNumber *)canGoBackForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)canGoForwardForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSNumber *)canGoForwardForWebViewWithIdentifier:(NSInteger)identifier error: (FlutterError *_Nullable *_Nonnull)error; -- (void)goBackForWebViewWithIdentifier:(NSNumber *)identifier +- (void)goBackForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)goForwardForWebViewWithIdentifier:(NSNumber *)identifier +- (void)goForwardForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)reloadWebViewWithIdentifier:(NSNumber *)identifier +- (void)reloadWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)titleForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSString *)titleForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setAllowsBackForwardForWebViewWithIdentifier:(NSNumber *)identifier - isAllowed:(NSNumber *)allow +- (void)setAllowsBackForwardForWebViewWithIdentifier:(NSInteger)identifier + isAllowed:(BOOL)allow error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setCustomUserAgentForWebViewWithIdentifier:(NSNumber *)identifier +- (void)setCustomUserAgentForWebViewWithIdentifier:(NSInteger)identifier userAgent:(nullable NSString *)userAgent error:(FlutterError *_Nullable *_Nonnull)error; -- (void)evaluateJavaScriptForWebViewWithIdentifier:(NSNumber *)identifier +- (void)evaluateJavaScriptForWebViewWithIdentifier:(NSInteger)identifier javaScriptString:(NSString *)javaScriptString completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; -- (void)setInspectableForWebViewWithIdentifier:(NSNumber *)identifier - inspectable:(NSNumber *)inspectable +- (void)setInspectableForWebViewWithIdentifier:(NSInteger)identifier + inspectable:(BOOL)inspectable error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)customUserAgentForWebViewWithIdentifier:(NSNumber *)identifier +- (nullable NSString *)customUserAgentForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull) error; @end -extern void FWFWKWebViewHostApiSetup(id binaryMessenger, +extern void SetUpFWFWKWebViewHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKUIDelegateHostApi. @@ -769,10 +835,10 @@ NSObject *FWFWKUIDelegateHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @protocol FWFWKUIDelegateHostApi -- (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFWKUIDelegateHostApiSetup(id binaryMessenger, +extern void SetUpFWFWKUIDelegateHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKUIDelegateFlutterApi. @@ -783,14 +849,14 @@ NSObject *FWFWKUIDelegateFlutterApiGetCodec(void); /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @interface FWFWKUIDelegateFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)onCreateWebViewForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier - configurationIdentifier:(NSNumber *)configurationIdentifier +- (void)onCreateWebViewForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier + configurationIdentifier:(NSInteger)configurationIdentifier navigationAction:(FWFWKNavigationActionData *)navigationAction completion:(void (^)(FlutterError *_Nullable))completion; /// Callback to Dart function `WKUIDelegate.requestMediaCapturePermission`. -- (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSNumber *)identifier - webViewIdentifier:(NSNumber *)webViewIdentifier +- (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier origin:(FWFWKSecurityOriginData *)origin frame:(FWFWKFrameInfoData *)frame type:(FWFWKMediaCaptureTypeData *)type @@ -807,15 +873,15 @@ NSObject *FWFWKHttpCookieStoreHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc. @protocol FWFWKHttpCookieStoreHostApi -- (void)createFromWebsiteDataStoreWithIdentifier:(NSNumber *)identifier - dataStoreIdentifier:(NSNumber *)websiteDataStoreIdentifier +- (void)createFromWebsiteDataStoreWithIdentifier:(NSInteger)identifier + dataStoreIdentifier:(NSInteger)websiteDataStoreIdentifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setCookieForStoreWithIdentifier:(NSNumber *)identifier +- (void)setCookieForStoreWithIdentifier:(NSInteger)identifier cookie:(FWFNSHttpCookieData *)cookie completion:(void (^)(FlutterError *_Nullable))completion; @end -extern void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger, +extern void SetUpFWFWKHttpCookieStoreHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFNSUrlHostApi. @@ -829,12 +895,12 @@ NSObject *FWFNSUrlHostApiGetCodec(void); /// /// See https://developer.apple.com/documentation/foundation/nsurl?language=objc. @protocol FWFNSUrlHostApi -- (nullable NSString *)absoluteStringForNSURLWithIdentifier:(NSNumber *)identifier +- (nullable NSString *)absoluteStringForNSURLWithIdentifier:(NSInteger)identifier error: (FlutterError *_Nullable *_Nonnull)error; @end -extern void FWFNSUrlHostApiSetup(id binaryMessenger, +extern void SetUpFWFNSUrlHostApi(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFNSUrlFlutterApi. @@ -849,7 +915,7 @@ NSObject *FWFNSUrlFlutterApiGetCodec(void); /// See https://developer.apple.com/documentation/foundation/nsurl?language=objc. @interface FWFNSUrlFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)createWithIdentifier:(NSNumber *)identifier +- (void)createWithIdentifier:(NSInteger)identifier completion:(void (^)(FlutterError *_Nullable))completion; @end 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 cc58067418e9..52fbbee9351e 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 (v10.1.4), 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,6 +16,154 @@ #error File requires ARC to be enabled. #endif +/// Mirror of NSKeyValueObservingOptions. +/// +/// See +/// https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc. +@implementation FWFNSKeyValueObservingOptionsEnumBox +- (instancetype)initWithValue:(FWFNSKeyValueObservingOptionsEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of NSKeyValueChange. +/// +/// See https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc. +@implementation FWFNSKeyValueChangeEnumBox +- (instancetype)initWithValue:(FWFNSKeyValueChangeEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of NSKeyValueChangeKey. +/// +/// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc. +@implementation FWFNSKeyValueChangeKeyEnumBox +- (instancetype)initWithValue:(FWFNSKeyValueChangeKeyEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of WKUserScriptInjectionTime. +/// +/// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc. +@implementation FWFWKUserScriptInjectionTimeEnumBox +- (instancetype)initWithValue:(FWFWKUserScriptInjectionTimeEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of WKAudiovisualMediaTypes. +/// +/// See +/// [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc). +@implementation FWFWKAudiovisualMediaTypeEnumBox +- (instancetype)initWithValue:(FWFWKAudiovisualMediaTypeEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of WKWebsiteDataTypes. +/// +/// See +/// https://developer.apple.com/documentation/webkit/wkwebsitedatarecord/data_store_record_types?language=objc. +@implementation FWFWKWebsiteDataTypeEnumBox +- (instancetype)initWithValue:(FWFWKWebsiteDataTypeEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of WKNavigationActionPolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +@implementation FWFWKNavigationActionPolicyEnumBox +- (instancetype)initWithValue:(FWFWKNavigationActionPolicyEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Mirror of NSHTTPCookiePropertyKey. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. +@implementation FWFNSHttpCookiePropertyKeyEnumBox +- (instancetype)initWithValue:(FWFNSHttpCookiePropertyKeyEnum)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// An object that contains information about an action that causes navigation +/// to occur. +/// +/// Wraps +/// [WKNavigationType](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc). +@implementation FWFWKNavigationTypeBox +- (instancetype)initWithValue:(FWFWKNavigationType)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// Possible permission decisions for device resource access. +/// +/// See https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc. +@implementation FWFWKPermissionDecisionBox +- (instancetype)initWithValue:(FWFWKPermissionDecision)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +/// List of the types of media devices that can capture audio, video, or both. +/// +/// See https://developer.apple.com/documentation/webkit/wkmediacapturetype?language=objc. +@implementation FWFWKMediaCaptureTypeBox +- (instancetype)initWithValue:(FWFWKMediaCaptureType)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ @@ -351,11 +499,9 @@ + (instancetype)makeWithUrl:(NSString *)url + (FWFNSUrlRequestData *)fromList:(NSArray *)list { FWFNSUrlRequestData *pigeonResult = [[FWFNSUrlRequestData alloc] init]; pigeonResult.url = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.url != nil, @""); pigeonResult.httpMethod = GetNullableObjectAtIndex(list, 1); pigeonResult.httpBody = GetNullableObjectAtIndex(list, 2); pigeonResult.allHttpHeaderFields = GetNullableObjectAtIndex(list, 3); - NSAssert(pigeonResult.allHttpHeaderFields != nil, @""); return pigeonResult; } + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list { @@ -363,10 +509,10 @@ + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.url ?: [NSNull null]), - (self.httpMethod ?: [NSNull null]), - (self.httpBody ?: [NSNull null]), - (self.allHttpHeaderFields ?: [NSNull null]), + self.url ?: [NSNull null], + self.httpMethod ?: [NSNull null], + self.httpBody ?: [NSNull null], + self.allHttpHeaderFields ?: [NSNull null], ]; } @end @@ -374,7 +520,7 @@ - (NSArray *)toList { @implementation FWFWKUserScriptData + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime - isMainFrameOnly:(NSNumber *)isMainFrameOnly { + isMainFrameOnly:(BOOL)isMainFrameOnly { FWFWKUserScriptData *pigeonResult = [[FWFWKUserScriptData alloc] init]; pigeonResult.source = source; pigeonResult.injectionTime = injectionTime; @@ -384,11 +530,9 @@ + (instancetype)makeWithSource:(NSString *)source + (FWFWKUserScriptData *)fromList:(NSArray *)list { FWFWKUserScriptData *pigeonResult = [[FWFWKUserScriptData alloc] init]; pigeonResult.source = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.source != nil, @""); pigeonResult.injectionTime = [FWFWKUserScriptInjectionTimeEnumData nullableFromList:(GetNullableObjectAtIndex(list, 1))]; - pigeonResult.isMainFrameOnly = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.isMainFrameOnly != nil, @""); + pigeonResult.isMainFrameOnly = [GetNullableObjectAtIndex(list, 2) boolValue]; return pigeonResult; } + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list { @@ -396,9 +540,9 @@ + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.source ?: [NSNull null]), + self.source ?: [NSNull null], (self.injectionTime ? [self.injectionTime toList] : [NSNull null]), - (self.isMainFrameOnly ?: [NSNull null]), + @(self.isMainFrameOnly), ]; } @end @@ -416,10 +560,8 @@ + (instancetype)makeWithRequest:(FWFNSUrlRequestData *)request + (FWFWKNavigationActionData *)fromList:(NSArray *)list { FWFWKNavigationActionData *pigeonResult = [[FWFWKNavigationActionData alloc] init]; pigeonResult.request = [FWFNSUrlRequestData nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - NSAssert(pigeonResult.request != nil, @""); pigeonResult.targetFrame = [FWFWKFrameInfoData nullableFromList:(GetNullableObjectAtIndex(list, 1))]; - NSAssert(pigeonResult.targetFrame != nil, @""); pigeonResult.navigationType = [GetNullableObjectAtIndex(list, 2) integerValue]; return pigeonResult; } @@ -436,15 +578,14 @@ - (NSArray *)toList { @end @implementation FWFWKFrameInfoData -+ (instancetype)makeWithIsMainFrame:(NSNumber *)isMainFrame { ++ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; pigeonResult.isMainFrame = isMainFrame; return pigeonResult; } + (FWFWKFrameInfoData *)fromList:(NSArray *)list { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; - pigeonResult.isMainFrame = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.isMainFrame != nil, @""); + pigeonResult.isMainFrame = [GetNullableObjectAtIndex(list, 0) boolValue]; return pigeonResult; } + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list { @@ -452,13 +593,13 @@ + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.isMainFrame ?: [NSNull null]), + @(self.isMainFrame), ]; } @end @implementation FWFNSErrorData -+ (instancetype)makeWithCode:(NSNumber *)code ++ (instancetype)makeWithCode:(NSInteger)code domain:(NSString *)domain userInfo:(nullable NSDictionary *)userInfo { FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init]; @@ -469,10 +610,8 @@ + (instancetype)makeWithCode:(NSNumber *)code } + (FWFNSErrorData *)fromList:(NSArray *)list { FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init]; - pigeonResult.code = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.code != nil, @""); + pigeonResult.code = [GetNullableObjectAtIndex(list, 0) integerValue]; pigeonResult.domain = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.domain != nil, @""); pigeonResult.userInfo = GetNullableObjectAtIndex(list, 2); return pigeonResult; } @@ -481,15 +620,15 @@ + (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.code ?: [NSNull null]), - (self.domain ?: [NSNull null]), - (self.userInfo ?: [NSNull null]), + @(self.code), + self.domain ?: [NSNull null], + self.userInfo ?: [NSNull null], ]; } @end @implementation FWFWKScriptMessageData -+ (instancetype)makeWithName:(NSString *)name body:(id)body { ++ (instancetype)makeWithName:(NSString *)name body:(nullable id)body { FWFWKScriptMessageData *pigeonResult = [[FWFWKScriptMessageData alloc] init]; pigeonResult.name = name; pigeonResult.body = body; @@ -498,7 +637,6 @@ + (instancetype)makeWithName:(NSString *)name body:(id)body { + (FWFWKScriptMessageData *)fromList:(NSArray *)list { FWFWKScriptMessageData *pigeonResult = [[FWFWKScriptMessageData alloc] init]; pigeonResult.name = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.name != nil, @""); pigeonResult.body = GetNullableObjectAtIndex(list, 1); return pigeonResult; } @@ -507,14 +645,14 @@ + (nullable FWFWKScriptMessageData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.name ?: [NSNull null]), - (self.body ?: [NSNull null]), + self.name ?: [NSNull null], + self.body ?: [NSNull null], ]; } @end @implementation FWFWKSecurityOriginData -+ (instancetype)makeWithHost:(NSString *)host port:(NSNumber *)port protocol:(NSString *)protocol { ++ (instancetype)makeWithHost:(NSString *)host port:(NSInteger)port protocol:(NSString *)protocol { FWFWKSecurityOriginData *pigeonResult = [[FWFWKSecurityOriginData alloc] init]; pigeonResult.host = host; pigeonResult.port = port; @@ -524,11 +662,8 @@ + (instancetype)makeWithHost:(NSString *)host port:(NSNumber *)port protocol:(NS + (FWFWKSecurityOriginData *)fromList:(NSArray *)list { FWFWKSecurityOriginData *pigeonResult = [[FWFWKSecurityOriginData alloc] init]; pigeonResult.host = GetNullableObjectAtIndex(list, 0); - NSAssert(pigeonResult.host != nil, @""); - pigeonResult.port = GetNullableObjectAtIndex(list, 1); - NSAssert(pigeonResult.port != nil, @""); + pigeonResult.port = [GetNullableObjectAtIndex(list, 1) integerValue]; pigeonResult.protocol = GetNullableObjectAtIndex(list, 2); - NSAssert(pigeonResult.protocol != nil, @""); return pigeonResult; } + (nullable FWFWKSecurityOriginData *)nullableFromList:(NSArray *)list { @@ -536,9 +671,9 @@ + (nullable FWFWKSecurityOriginData *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.host ?: [NSNull null]), - (self.port ?: [NSNull null]), - (self.protocol ?: [NSNull null]), + self.host ?: [NSNull null], + @(self.port), + self.protocol ?: [NSNull null], ]; } @end @@ -554,9 +689,7 @@ + (instancetype)makeWithPropertyKeys:(NSArray binaryMessenger, +void SetUpFWFWKWebsiteDataStoreHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -660,8 +792,8 @@ void FWFWKWebsiteDataStoreHostApiSetup(id binaryMessenge api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_configurationIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier @@ -685,7 +817,7 @@ void FWFWKWebsiteDataStoreHostApiSetup(id binaryMessenge api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api createDefaultDataStoreWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -709,9 +841,10 @@ void FWFWKWebsiteDataStoreHostApiSetup(id binaryMessenge api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSArray *arg_dataTypes = GetNullableObjectAtIndex(args, 1); - NSNumber *arg_modificationTimeInSecondsSinceEpoch = GetNullableObjectAtIndex(args, 2); + double arg_modificationTimeInSecondsSinceEpoch = + [GetNullableObjectAtIndex(args, 2) doubleValue]; [api removeDataFromDataStoreWithIdentifier:arg_identifier ofTypes:arg_dataTypes modifiedSince:arg_modificationTimeInSecondsSinceEpoch @@ -731,7 +864,7 @@ void FWFWKWebsiteDataStoreHostApiSetup(id binaryMessenge return sSharedObject; } -void FWFUIViewHostApiSetup(id binaryMessenger, +void SetUpFWFUIViewHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -747,7 +880,7 @@ void FWFUIViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSNumber *arg_value = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setBackgroundColorForViewWithIdentifier:arg_identifier toValue:arg_value error:&error]; @@ -769,8 +902,8 @@ void FWFUIViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_opaque = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_opaque = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setOpaqueForViewWithIdentifier:arg_identifier isOpaque:arg_opaque error:&error]; callback(wrapResult(nil, error)); @@ -786,7 +919,7 @@ void FWFUIViewHostApiSetup(id binaryMessenger, return sSharedObject; } -void FWFUIScrollViewHostApiSetup(id binaryMessenger, +void SetUpFWFUIScrollViewHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -802,8 +935,8 @@ void FWFUIScrollViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_webViewIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_webViewIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebViewWithIdentifier:arg_identifier webViewIdentifier:arg_webViewIdentifier @@ -827,7 +960,7 @@ void FWFUIScrollViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSArray *output = [api contentOffsetForScrollViewWithIdentifier:arg_identifier error:&error]; @@ -849,9 +982,9 @@ void FWFUIScrollViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_x = GetNullableObjectAtIndex(args, 1); - NSNumber *arg_y = GetNullableObjectAtIndex(args, 2); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + double arg_x = [GetNullableObjectAtIndex(args, 1) doubleValue]; + double arg_y = [GetNullableObjectAtIndex(args, 2) doubleValue]; FlutterError *error; [api scrollByForScrollViewWithIdentifier:arg_identifier x:arg_x y:arg_y error:&error]; callback(wrapResult(nil, error)); @@ -874,9 +1007,9 @@ void FWFUIScrollViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_x = GetNullableObjectAtIndex(args, 1); - NSNumber *arg_y = GetNullableObjectAtIndex(args, 2); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + double arg_x = [GetNullableObjectAtIndex(args, 1) doubleValue]; + double arg_y = [GetNullableObjectAtIndex(args, 2) doubleValue]; FlutterError *error; [api setContentOffsetForScrollViewWithIdentifier:arg_identifier toX:arg_x @@ -937,7 +1070,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FWFWKWebViewConfigurationHostApiSetup(id binaryMessenger, +void SetUpFWFWKWebViewConfigurationHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -952,7 +1085,7 @@ void FWFWKWebViewConfigurationHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -975,8 +1108,8 @@ void FWFWKWebViewConfigurationHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_webViewIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_webViewIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebViewWithIdentifier:arg_identifier webViewIdentifier:arg_webViewIdentifier @@ -1002,8 +1135,8 @@ void FWFWKWebViewConfigurationHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_allow = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_allow = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:arg_identifier isAllowed:arg_allow @@ -1030,8 +1163,8 @@ void FWFWKWebViewConfigurationHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_limit = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_limit = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:arg_identifier isLimited:arg_limit @@ -1058,7 +1191,7 @@ void FWFWKWebViewConfigurationHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSArray *arg_types = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setMediaTypesRequiresUserActionForConfigurationWithIdentifier:arg_identifier @@ -1090,17 +1223,30 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)createWithIdentifier:(NSNumber *)arg_identifier +- (void)createWithIdentifier:(NSInteger)arg_identifier completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFWKWebViewConfigurationFlutterApiGetCodec()]; - [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] - reply:^(id reply) { - completion(nil); - }]; + [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 @@ -1157,7 +1303,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FWFWKUserContentControllerHostApiSetup(id binaryMessenger, +void SetUpFWFWKUserContentControllerHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1174,8 +1320,8 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_configurationIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier @@ -1202,8 +1348,8 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_handlerIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_handlerIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; NSString *arg_name = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api addScriptMessageHandlerForControllerWithIdentifier:arg_identifier @@ -1230,7 +1376,7 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_name = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api removeScriptMessageHandlerForControllerWithIdentifier:arg_identifier @@ -1256,7 +1402,7 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api removeAllScriptMessageHandlersForControllerWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -1279,7 +1425,7 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FWFWKUserScriptData *arg_userScript = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api addUserScriptForControllerWithIdentifier:arg_identifier @@ -1305,7 +1451,7 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api removeAllUserScriptsForControllerWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -1321,7 +1467,7 @@ void FWFWKUserContentControllerHostApiSetup(id binaryMes return sSharedObject; } -void FWFWKPreferencesHostApiSetup(id binaryMessenger, +void SetUpFWFWKPreferencesHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1338,8 +1484,8 @@ void FWFWKPreferencesHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_configurationIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier @@ -1364,8 +1510,8 @@ void FWFWKPreferencesHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_enabled = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_enabled = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setJavaScriptEnabledForPreferencesWithIdentifier:arg_identifier isEnabled:arg_enabled @@ -1383,7 +1529,7 @@ void FWFWKPreferencesHostApiSetup(id binaryMessenger, return sSharedObject; } -void FWFWKScriptMessageHandlerHostApiSetup(id binaryMessenger, +void SetUpFWFWKScriptMessageHandlerHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1398,7 +1544,7 @@ void FWFWKScriptMessageHandlerHostApiSetup(id binaryMess api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -1470,8 +1616,8 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina return self; } - (void) - didReceiveScriptMessageForHandlerWithIdentifier:(NSNumber *)arg_identifier - userContentControllerIdentifier:(NSNumber *)arg_userContentControllerIdentifier + didReceiveScriptMessageForHandlerWithIdentifier:(NSInteger)arg_identifier + userContentControllerIdentifier:(NSInteger)arg_userContentControllerIdentifier message:(FWFWKScriptMessageData *)arg_message completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel @@ -1479,13 +1625,25 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina @"WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage" binaryMessenger:self.binaryMessenger codec:FWFWKScriptMessageHandlerFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_userContentControllerIdentifier ?: [NSNull null], - arg_message ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; + [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 @@ -1495,7 +1653,7 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina return sSharedObject; } -void FWFWKNavigationDelegateHostApiSetup(id binaryMessenger, +void SetUpFWFWKNavigationDelegateHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1510,7 +1668,7 @@ void FWFWKNavigationDelegateHostApiSetup(id binaryMessen api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -1601,8 +1759,8 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier +- (void)didFinishNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel @@ -1610,16 +1768,26 @@ - (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier @"WKNavigationDelegateFlutterApi.didFinishNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_url ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier + [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 { @@ -1628,16 +1796,26 @@ - (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_i @"WKNavigationDelegateFlutterApi.didStartProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_url ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier + [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 navigationAction:(FWFWKNavigationActionData *) arg_navigationAction completion: @@ -1651,16 +1829,29 @@ - (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)arg binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_navigationAction ?: [NSNull null] + @(arg_identifier), @(arg_webViewIdentifier), arg_navigationAction ?: [NSNull null] ] - reply:^(id reply) { - FWFWKNavigationActionPolicyEnumData *output = reply; - completion(output, nil); + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + FWFWKNavigationActionPolicyEnumData *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)didFailNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier +- (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier error:(FWFNSErrorData *)arg_error completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel @@ -1668,16 +1859,26 @@ - (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier @"WKNavigationDelegateFlutterApi.didFailNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_error ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier + [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 { @@ -1686,17 +1887,27 @@ - (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_id @"WKNavigationDelegateFlutterApi.didFailProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_error ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSNumber *)arg_identifier + [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: - (NSNumber *)arg_webViewIdentifier + (NSInteger)arg_webViewIdentifier completion: (void (^)(FlutterError *_Nullable)) completion { @@ -1705,10 +1916,23 @@ - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSNumber @"WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null] ] - reply:^(id reply) { - completion(nil); - }]; + [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:@""]); + } + }]; } @end @@ -1760,7 +1984,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FWFNSObjectHostApiSetup(id binaryMessenger, +void SetUpFWFNSObjectHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -1774,7 +1998,7 @@ void FWFNSObjectHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api disposeObjectWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -1798,8 +2022,8 @@ void FWFNSObjectHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_observerIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_observerIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; NSString *arg_keyPath = GetNullableObjectAtIndex(args, 2); NSArray *arg_options = GetNullableObjectAtIndex(args, 3); @@ -1830,8 +2054,8 @@ void FWFNSObjectHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_observerIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_observerIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; NSString *arg_keyPath = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api removeObserverForObjectWithIdentifier:arg_identifier @@ -1911,9 +2135,9 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)observeValueForObjectWithIdentifier:(NSNumber *)arg_identifier +- (void)observeValueForObjectWithIdentifier:(NSInteger)arg_identifier keyPath:(NSString *)arg_keyPath - objectIdentifier:(NSNumber *)arg_objectIdentifier + objectIdentifier:(NSInteger)arg_objectIdentifier changeKeys: (NSArray *)arg_changeKeys changeValues:(NSArray *)arg_changeValues @@ -1923,26 +2147,51 @@ - (void)observeValueForObjectWithIdentifier:(NSNumber *)arg_identifier @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.observeValue" binaryMessenger:self.binaryMessenger codec:FWFNSObjectFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_keyPath ?: [NSNull null], - arg_objectIdentifier ?: [NSNull null], arg_changeKeys ?: [NSNull null], - arg_changeValues ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)disposeObjectWithIdentifier:(NSNumber *)arg_identifier + [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 { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.dispose" binaryMessenger:self.binaryMessenger codec:FWFNSObjectFlutterApiGetCodec()]; - [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] - reply:^(id reply) { - completion(nil); - }]; + [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 @@ -2079,7 +2328,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FWFWKWebViewHostApiSetup(id binaryMessenger, +void SetUpFWFWKWebViewHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -2094,8 +2343,8 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_configurationIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier @@ -2120,7 +2369,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSNumber *arg_uiDelegateIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setUIDelegateForWebViewWithIdentifier:arg_identifier @@ -2147,7 +2396,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSNumber *arg_navigationDelegateIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setNavigationDelegateForWebViewWithIdentifier:arg_identifier @@ -2171,7 +2420,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSString *output = [api URLForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); @@ -2194,7 +2443,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api estimatedProgressForWebViewWithIdentifier:arg_identifier error:&error]; @@ -2217,7 +2466,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FWFNSUrlRequestData *arg_request = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api loadRequestForWebViewWithIdentifier:arg_identifier request:arg_request error:&error]; @@ -2241,7 +2490,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_string = GetNullableObjectAtIndex(args, 1); NSString *arg_baseUrl = GetNullableObjectAtIndex(args, 2); FlutterError *error; @@ -2268,7 +2517,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_url = GetNullableObjectAtIndex(args, 1); NSString *arg_readAccessUrl = GetNullableObjectAtIndex(args, 2); FlutterError *error; @@ -2296,7 +2545,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_key = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api loadAssetForWebViewWithIdentifier:arg_identifier assetKey:arg_key error:&error]; @@ -2318,7 +2567,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api canGoBackForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); @@ -2340,7 +2589,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api canGoForwardForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); @@ -2361,7 +2610,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api goBackForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -2382,7 +2631,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api goForwardForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -2403,7 +2652,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api reloadWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -2424,7 +2673,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSString *output = [api titleForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); @@ -2447,8 +2696,8 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_allow = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_allow = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setAllowsBackForwardForWebViewWithIdentifier:arg_identifier isAllowed:arg_allow @@ -2473,7 +2722,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_userAgent = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setCustomUserAgentForWebViewWithIdentifier:arg_identifier @@ -2500,7 +2749,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; NSString *arg_javaScriptString = GetNullableObjectAtIndex(args, 1); [api evaluateJavaScriptForWebViewWithIdentifier:arg_identifier javaScriptString:arg_javaScriptString @@ -2527,8 +2776,8 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_inspectable = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + BOOL arg_inspectable = [GetNullableObjectAtIndex(args, 1) boolValue]; FlutterError *error; [api setInspectableForWebViewWithIdentifier:arg_identifier inspectable:arg_inspectable @@ -2552,7 +2801,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSString *output = [api customUserAgentForWebViewWithIdentifier:arg_identifier error:&error]; @@ -2569,7 +2818,7 @@ void FWFWKWebViewHostApiSetup(id binaryMessenger, return sSharedObject; } -void FWFWKUIDelegateHostApiSetup(id binaryMessenger, +void SetUpFWFWKUIDelegateHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -2583,7 +2832,7 @@ void FWFWKUIDelegateHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); @@ -2679,9 +2928,9 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)onCreateWebViewForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier - configurationIdentifier:(NSNumber *)arg_configurationIdentifier +- (void)onCreateWebViewForDelegateWithIdentifier:(NSInteger)arg_identifier + webViewIdentifier:(NSInteger)arg_webViewIdentifier + configurationIdentifier:(NSInteger)arg_configurationIdentifier navigationAction:(FWFWKNavigationActionData *)arg_navigationAction completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel @@ -2689,16 +2938,29 @@ - (void)onCreateWebViewForDelegateWithIdentifier:(NSNumber *)arg_identifier @"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.onCreateWebView" binaryMessenger:self.binaryMessenger codec:FWFWKUIDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_configurationIdentifier ?: [NSNull null], arg_navigationAction ?: [NSNull null] - ] - reply:^(id reply) { - completion(nil); - }]; -} -- (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSNumber *)arg_identifier - webViewIdentifier:(NSNumber *)arg_webViewIdentifier + [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 origin:(FWFWKSecurityOriginData *)arg_origin frame:(FWFWKFrameInfoData *)arg_frame type:(FWFWKMediaCaptureTypeData *)arg_type @@ -2711,14 +2973,28 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSNumber *)arg_i @"requestMediaCapturePermission" binaryMessenger:self.binaryMessenger codec:FWFWKUIDelegateFlutterApiGetCodec()]; - [channel sendMessage:@[ - arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], - arg_origin ?: [NSNull null], arg_frame ?: [NSNull null], arg_type ?: [NSNull null] - ] - reply:^(id reply) { - FWFWKPermissionDecisionData *output = reply; - completion(output, nil); - }]; + [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:@""]); + } + }]; } @end @@ -2775,7 +3051,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { return sSharedObject; } -void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger, +void SetUpFWFWKHttpCookieStoreHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -2791,8 +3067,8 @@ void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_websiteDataStoreIdentifier = GetNullableObjectAtIndex(args, 1); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSInteger arg_websiteDataStoreIdentifier = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; [api createFromWebsiteDataStoreWithIdentifier:arg_identifier dataStoreIdentifier:arg_websiteDataStoreIdentifier @@ -2817,7 +3093,7 @@ void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FWFNSHttpCookieData *arg_cookie = GetNullableObjectAtIndex(args, 1); [api setCookieForStoreWithIdentifier:arg_identifier cookie:arg_cookie @@ -2836,7 +3112,7 @@ void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger return sSharedObject; } -void FWFNSUrlHostApiSetup(id binaryMessenger, +void SetUpFWFNSUrlHostApi(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] @@ -2851,7 +3127,7 @@ void FWFNSUrlHostApiSetup(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); + NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSString *output = [api absoluteStringForNSURLWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); @@ -2880,15 +3156,28 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)createWithIdentifier:(NSNumber *)arg_identifier +- (void)createWithIdentifier:(NSInteger)arg_identifier completion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFNSUrlFlutterApiGetCodec()]; - [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] - reply:^(id reply) { - completion(nil); - }]; + [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 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.m index a5f4e02b9c2f..54703ff15b70 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.m @@ -20,21 +20,20 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (WKHTTPCookieStore *)HTTPCookieStoreForIdentifier:(NSNumber *)identifier { - return (WKHTTPCookieStore *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (WKHTTPCookieStore *)HTTPCookieStoreForIdentifier:(NSInteger)identifier { + return (WKHTTPCookieStore *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createFromWebsiteDataStoreWithIdentifier:(nonnull NSNumber *)identifier - dataStoreIdentifier:(nonnull NSNumber *)websiteDataStoreIdentifier +- (void)createFromWebsiteDataStoreWithIdentifier:(NSInteger)identifier + dataStoreIdentifier:(NSInteger)websiteDataStoreIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { - WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[self.instanceManager - instanceForIdentifier:websiteDataStoreIdentifier.longValue]; - [self.instanceManager addDartCreatedInstance:dataStore.httpCookieStore - withIdentifier:identifier.longValue]; + WKWebsiteDataStore *dataStore = + (WKWebsiteDataStore *)[self.instanceManager instanceForIdentifier:websiteDataStoreIdentifier]; + [self.instanceManager addDartCreatedInstance:dataStore.httpCookieStore withIdentifier:identifier]; } -- (void)setCookieForStoreWithIdentifier:(nonnull NSNumber *)identifier +- (void)setCookieForStoreWithIdentifier:(NSInteger)identifier cookie:(nonnull FWFNSHttpCookieData *)cookie completion:(nonnull void (^)(FlutterError *_Nullable))completion { NSHTTPCookie *nsCookie = FWFNativeNSHTTPCookieFromCookieData(cookie); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m index 242a5f707348..c2af8444a425 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m @@ -51,7 +51,6 @@ @interface FWFInstanceManager () @property NSMapTable *identifiers; @property NSMapTable *weakInstances; @property NSMapTable *strongInstances; -@property long nextIdentifier; @end @implementation FWFInstanceManager 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 c33388b1a0ec..63480ce18018 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,6 +7,11 @@ NS_ASSUME_NONNULL_BEGIN @interface FWFInstanceManager () +/** + * The next identifier that will be used for a host-created instance. + */ +@property long nextIdentifier; + /** * The number of instances stored as a strong reference. * 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 439a8eb2ed36..2b64e0b5ec2c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m @@ -29,9 +29,9 @@ - (void)didFinishNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView URL:(NSString *)URL completion:(void (^)(FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); - [self didFinishNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; + [self didFinishNavigationForDelegateWithIdentifier:[self identifierForDelegate:instance] webViewIdentifier:webViewIdentifier URL:URL completion:completion]; @@ -41,10 +41,9 @@ - (void)didStartProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instan webView:(WKWebView *)webView URL:(NSString *)URL completion:(void (^)(FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); - [self didStartProvisionalNavigationForDelegateWithIdentifier:@([self - identifierForDelegate:instance]) + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; + [self didStartProvisionalNavigationForDelegateWithIdentifier:[self identifierForDelegate:instance] webViewIdentifier:webViewIdentifier URL:URL completion:completion]; @@ -57,13 +56,12 @@ - (void)didStartProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instan completion: (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; FWFWKNavigationActionData *navigationActionData = FWFWKNavigationActionDataFromNativeWKNavigationAction(navigationAction); [self - decidePolicyForNavigationActionForDelegateWithIdentifier:@([self - identifierForDelegate:instance]) + decidePolicyForNavigationActionForDelegateWithIdentifier:[self identifierForDelegate:instance] webViewIdentifier:webViewIdentifier navigationAction:navigationActionData completion:completion]; @@ -73,9 +71,9 @@ - (void)didFailNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView error:(NSError *)error completion:(void (^)(FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); - [self didFailNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; + [self didFailNavigationForDelegateWithIdentifier:[self identifierForDelegate:instance] webViewIdentifier:webViewIdentifier error:FWFNSErrorDataFromNativeNSError(error) completion:completion]; @@ -85,23 +83,22 @@ - (void)didFailProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instanc webView:(WKWebView *)webView error:(NSError *)error completion:(void (^)(FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); - [self - didFailProvisionalNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) - webViewIdentifier:webViewIdentifier - error:FWFNSErrorDataFromNativeNSError(error) - completion:completion]; + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; + [self didFailProvisionalNavigationForDelegateWithIdentifier:[self identifierForDelegate:instance] + webViewIdentifier:webViewIdentifier + error:FWFNSErrorDataFromNativeNSError(error) + completion:completion]; } - (void)webViewWebContentProcessDidTerminateForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView completion: (void (^)(FlutterError *_Nullable))completion { - NSNumber *webViewIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); + NSInteger webViewIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:webView]; [self webViewWebContentProcessDidTerminateForDelegateWithIdentifier: - @([self identifierForDelegate:instance]) + [self identifierForDelegate:instance] webViewIdentifier:webViewIdentifier completion:completion]; } @@ -239,16 +236,15 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen return self; } -- (FWFNavigationDelegate *)navigationDelegateForIdentifier:(NSNumber *)identifier { - return (FWFNavigationDelegate *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (FWFNavigationDelegate *)navigationDelegateForIdentifier:(NSInteger)identifier { + return (FWFNavigationDelegate *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { FWFNavigationDelegate *navigationDelegate = [[FWFNavigationDelegate alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; - [self.instanceManager addDartCreatedInstance:navigationDelegate - withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:navigationDelegate withIdentifier:identifier]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m index 3adf7246a9a0..098d291c6c2b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m @@ -58,12 +58,12 @@ - (void)observeValueForObject:(NSObject *)instance ? @([self.instanceManager identifierWithStrongReferenceForInstance:value]) : value; [changeValues addObject:[FWFObjectOrIdentifier makeWithValue:returnValue - isIdentifier:@(isIdentifier)]]; + isIdentifier:isIdentifier]]; }]; - NSNumber *objectIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:object]); - [self observeValueForObjectWithIdentifier:@([self identifierForObject:instance]) + NSInteger objectIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:object]; + [self observeValueForObjectWithIdentifier:[self identifierForObject:instance] keyPath:keyPath objectIdentifier:objectIdentifier changeKeys:changeKeys @@ -111,12 +111,12 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (NSObject *)objectForIdentifier:(NSNumber *)identifier { - return (NSObject *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (NSObject *)objectForIdentifier:(NSInteger)identifier { + return (NSObject *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)addObserverForObjectWithIdentifier:(nonnull NSNumber *)identifier - observerIdentifier:(nonnull NSNumber *)observer +- (void)addObserverForObjectWithIdentifier:(NSInteger)identifier + observerIdentifier:(NSInteger)observer keyPath:(nonnull NSString *)keyPath options: (nonnull NSArray *) @@ -132,16 +132,16 @@ - (void)addObserverForObjectWithIdentifier:(nonnull NSNumber *)identifier context:nil]; } -- (void)removeObserverForObjectWithIdentifier:(nonnull NSNumber *)identifier - observerIdentifier:(nonnull NSNumber *)observer +- (void)removeObserverForObjectWithIdentifier:(NSInteger)identifier + observerIdentifier:(NSInteger)observer keyPath:(nonnull NSString *)keyPath error:(FlutterError *_Nullable *_Nonnull)error { [[self objectForIdentifier:identifier] removeObserver:[self objectForIdentifier:observer] forKeyPath:keyPath]; } -- (void)disposeObjectWithIdentifier:(nonnull NSNumber *)identifier +- (void)disposeObjectWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { - [self.instanceManager removeInstanceWithIdentifier:identifier.longValue]; + [self.instanceManager removeInstanceWithIdentifier:identifier]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.m index 1a10c08eec4a..95078975f4d9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.m @@ -19,28 +19,26 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (WKPreferences *)preferencesForIdentifier:(NSNumber *)identifier { - return (WKPreferences *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (WKPreferences *)preferencesForIdentifier:(NSInteger)identifier { + return (WKPreferences *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier - error:(FlutterError *_Nullable *_Nonnull)error { +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { WKPreferences *preferences = [[WKPreferences alloc] init]; - [self.instanceManager addDartCreatedInstance:preferences withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:preferences withIdentifier:identifier]; } -- (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier - configurationIdentifier:(nonnull NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager - instanceForIdentifier:configurationIdentifier.longValue]; - [self.instanceManager addDartCreatedInstance:configuration.preferences - withIdentifier:identifier.longValue]; + instanceForIdentifier:configurationIdentifier]; + [self.instanceManager addDartCreatedInstance:configuration.preferences withIdentifier:identifier]; } -- (void)setJavaScriptEnabledForPreferencesWithIdentifier:(nonnull NSNumber *)identifier - isEnabled:(nonnull NSNumber *)enabled +- (void)setJavaScriptEnabledForPreferencesWithIdentifier:(NSInteger)identifier + isEnabled:(BOOL)enabled error:(FlutterError *_Nullable *_Nonnull)error { - [[self preferencesForIdentifier:identifier] setJavaScriptEnabled:enabled.boolValue]; + [[self preferencesForIdentifier:identifier] setJavaScriptEnabled:enabled]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.m index f1b83993c4f8..1e27c1208bd0 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.m @@ -28,10 +28,10 @@ - (void)didReceiveScriptMessageForHandler:(FWFScriptMessageHandler *)instance userContentController:(WKUserContentController *)userContentController message:(WKScriptMessage *)message completion:(void (^)(FlutterError *_Nullable))completion { - NSNumber *userContentControllerIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:userContentController]); + NSInteger userContentControllerIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:userContentController]; FWFWKScriptMessageData *messageData = FWFWKScriptMessageDataFromNativeWKScriptMessage(message); - [self didReceiveScriptMessageForHandlerWithIdentifier:@([self identifierForHandler:instance]) + [self didReceiveScriptMessageForHandlerWithIdentifier:[self identifierForHandler:instance] userContentControllerIdentifier:userContentControllerIdentifier message:messageData completion:completion]; @@ -85,12 +85,10 @@ - (FWFScriptMessageHandler *)scriptMessageHandlerForIdentifier:(NSNumber *)ident instanceForIdentifier:identifier.longValue]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier - error:(FlutterError *_Nullable *_Nonnull)error { +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFScriptMessageHandler *scriptMessageHandler = [[FWFScriptMessageHandler alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; - [self.instanceManager addDartCreatedInstance:scriptMessageHandler - withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:scriptMessageHandler withIdentifier:identifier]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.m index a32e9565b514..18287d40ea00 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.m @@ -19,41 +19,37 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (UIScrollView *)scrollViewForIdentifier:(NSNumber *)identifier { - return (UIScrollView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (UIScrollView *)scrollViewForIdentifier:(NSInteger)identifier { + return (UIScrollView *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createFromWebViewWithIdentifier:(nonnull NSNumber *)identifier - webViewIdentifier:(nonnull NSNumber *)webViewIdentifier +- (void)createFromWebViewWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - WKWebView *webView = - (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier.longValue]; - [self.instanceManager addDartCreatedInstance:webView.scrollView - withIdentifier:identifier.longValue]; + WKWebView *webView = (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier]; + [self.instanceManager addDartCreatedInstance:webView.scrollView withIdentifier:identifier]; } - (NSArray *) - contentOffsetForScrollViewWithIdentifier:(nonnull NSNumber *)identifier + contentOffsetForScrollViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { CGPoint point = [[self scrollViewForIdentifier:identifier] contentOffset]; return @[ @(point.x), @(point.y) ]; } -- (void)scrollByForScrollViewWithIdentifier:(nonnull NSNumber *)identifier - x:(nonnull NSNumber *)x - y:(nonnull NSNumber *)y +- (void)scrollByForScrollViewWithIdentifier:(NSInteger)identifier + x:(double)x + y:(double)y error:(FlutterError *_Nullable *_Nonnull)error { UIScrollView *scrollView = [self scrollViewForIdentifier:identifier]; CGPoint contentOffset = scrollView.contentOffset; - [scrollView setContentOffset:CGPointMake(contentOffset.x + x.doubleValue, - contentOffset.y + y.doubleValue)]; + [scrollView setContentOffset:CGPointMake(contentOffset.x + x, contentOffset.y + y)]; } -- (void)setContentOffsetForScrollViewWithIdentifier:(nonnull NSNumber *)identifier - toX:(nonnull NSNumber *)x - y:(nonnull NSNumber *)y +- (void)setContentOffsetForScrollViewWithIdentifier:(NSInteger)identifier + toX:(double)x + y:(double)y error:(FlutterError *_Nullable *_Nonnull)error { - [[self scrollViewForIdentifier:identifier] - setContentOffset:CGPointMake(x.doubleValue, y.doubleValue)]; + [[self scrollViewForIdentifier:identifier] setContentOffset:CGPointMake(x, y)]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m index a7010509ef22..879d85dd9d42 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m @@ -43,18 +43,18 @@ - (void)onCreateWebViewForDelegate:(FWFUIDelegate *)instance }]; } - NSNumber *configurationIdentifier = - @([self.instanceManager identifierWithStrongReferenceForInstance:configuration]); + NSInteger configurationIdentifier = + [self.instanceManager identifierWithStrongReferenceForInstance:configuration]; FWFWKNavigationActionData *navigationActionData = FWFWKNavigationActionDataFromNativeWKNavigationAction(navigationAction); - [self onCreateWebViewForDelegateWithIdentifier:@([self identifierForDelegate:instance]) - webViewIdentifier: - @([self.instanceManager - identifierWithStrongReferenceForInstance:webView]) - configurationIdentifier:configurationIdentifier - navigationAction:navigationActionData - completion:completion]; + [self + onCreateWebViewForDelegateWithIdentifier:[self identifierForDelegate:instance] + webViewIdentifier:[self.instanceManager + identifierWithStrongReferenceForInstance:webView] + configurationIdentifier:configurationIdentifier + navigationAction:navigationActionData + completion:completion]; } - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(FWFUIDelegate *)instance @@ -66,12 +66,10 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(FWFUIDelegate *) (void (^)(WKPermissionDecision))completion API_AVAILABLE(ios(15.0)) { [self - requestMediaCapturePermissionForDelegateWithIdentifier:@([self - identifierForDelegate:instance]) + requestMediaCapturePermissionForDelegateWithIdentifier:[self identifierForDelegate:instance] webViewIdentifier: - @([self.instanceManager - identifierWithStrongReferenceForInstance: - webView]) + [self.instanceManager + identifierWithStrongReferenceForInstance:webView] origin: FWFWKSecurityOriginDataFromNativeWKSecurityOrigin( origin) @@ -158,10 +156,9 @@ - (FWFUIDelegate *)delegateForIdentifier:(NSNumber *)identifier { return (FWFUIDelegate *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier - error:(FlutterError *_Nullable *_Nonnull)error { +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFUIDelegate *uIDelegate = [[FWFUIDelegate alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; - [self.instanceManager addDartCreatedInstance:uIDelegate withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:uIDelegate withIdentifier:identifier]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.m index 1e168d0a8fcb..3ee6f38faa80 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.m @@ -18,11 +18,11 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (UIView *)viewForIdentifier:(NSNumber *)identifier { - return (UIView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (UIView *)viewForIdentifier:(NSInteger)identifier { + return (UIView *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)setBackgroundColorForViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)setBackgroundColorForViewWithIdentifier:(NSInteger)identifier toValue:(nullable NSNumber *)color error:(FlutterError *_Nullable *_Nonnull)error { if (color == nil) { @@ -36,9 +36,9 @@ - (void)setBackgroundColorForViewWithIdentifier:(nonnull NSNumber *)identifier [[self viewForIdentifier:identifier] setBackgroundColor:colorObject]; } -- (void)setOpaqueForViewWithIdentifier:(nonnull NSNumber *)identifier - isOpaque:(nonnull NSNumber *)opaque +- (void)setOpaqueForViewWithIdentifier:(NSInteger)identifier + isOpaque:(BOOL)opaque error:(FlutterError *_Nullable *_Nonnull)error { - [[self viewForIdentifier:identifier] setOpaque:opaque.boolValue]; + [[self viewForIdentifier:identifier] setOpaque:opaque]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.m index 9f747c22994b..c3101c7bfaaf 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFURLHostApi.m @@ -29,7 +29,7 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen } - (nullable NSString *) - absoluteStringForNSURLWithIdentifier:(nonnull NSNumber *)identifier + absoluteStringForNSURLWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { NSURL *instance = [self urlForIdentifier:identifier error:error]; if (*error) { @@ -39,14 +39,14 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen return instance.absoluteString; } -- (nullable NSURL *)urlForIdentifier:(NSNumber *)identifier +- (nullable NSURL *)urlForIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - NSURL *instance = (NSURL *)[self.instanceManager instanceForIdentifier:identifier.longValue]; + NSURL *instance = (NSURL *)[self.instanceManager instanceForIdentifier:identifier]; if (!instance) { - NSString *message = - [NSString stringWithFormat:@"InstanceManager does not contain an NSURL with identifier: %@", - identifier]; + NSString *message = [NSString + stringWithFormat:@"InstanceManager does not contain an NSURL with identifier: %li", + (long)identifier]; *error = [FlutterError errorWithCode:NSInternalInconsistencyException message:message details:nil]; @@ -68,7 +68,7 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen } - (void)create:(NSURL *)instance completion:(void (^)(FlutterError *_Nullable))completion { - [self.api createWithIdentifier:@([self.instanceManager addHostCreatedInstance:instance]) + [self.api createWithIdentifier:[self.instanceManager addHostCreatedInstance:instance] completion:completion]; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.m index ac314323da3b..074a54c9a8a9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.m @@ -20,39 +20,38 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (WKUserContentController *)userContentControllerForIdentifier:(NSNumber *)identifier { - return (WKUserContentController *)[self.instanceManager - instanceForIdentifier:identifier.longValue]; +- (WKUserContentController *)userContentControllerForIdentifier:(NSInteger)identifier { + return (WKUserContentController *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier - configurationIdentifier:(nonnull NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager - instanceForIdentifier:configurationIdentifier.longValue]; + instanceForIdentifier:configurationIdentifier]; [self.instanceManager addDartCreatedInstance:configuration.userContentController - withIdentifier:identifier.longValue]; + withIdentifier:identifier]; } -- (void)addScriptMessageHandlerForControllerWithIdentifier:(nonnull NSNumber *)identifier - handlerIdentifier:(nonnull NSNumber *)handler +- (void)addScriptMessageHandlerForControllerWithIdentifier:(NSInteger)identifier + handlerIdentifier:(NSInteger)handler ofName:(nonnull NSString *)name error: (FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] addScriptMessageHandler:(id)[self.instanceManager - instanceForIdentifier:handler.longValue] + instanceForIdentifier:handler] name:name]; } -- (void)removeScriptMessageHandlerForControllerWithIdentifier:(nonnull NSNumber *)identifier +- (void)removeScriptMessageHandlerForControllerWithIdentifier:(NSInteger)identifier name:(nonnull NSString *)name error:(FlutterError *_Nullable *_Nonnull) error { [[self userContentControllerForIdentifier:identifier] removeScriptMessageHandlerForName:name]; } -- (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(nonnull NSNumber *)identifier +- (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(NSInteger)identifier error: (FlutterError *_Nullable *_Nonnull) error { @@ -66,14 +65,14 @@ - (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(nonnull NSNum } } -- (void)addUserScriptForControllerWithIdentifier:(nonnull NSNumber *)identifier +- (void)addUserScriptForControllerWithIdentifier:(NSInteger)identifier userScript:(nonnull FWFWKUserScriptData *)userScript error:(FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] addUserScript:FWFNativeWKUserScriptFromScriptData(userScript)]; } -- (void)removeAllUserScriptsForControllerWithIdentifier:(nonnull NSNumber *)identifier +- (void)removeAllUserScriptsForControllerWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] removeAllUserScripts]; } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.m index 762c07b5abe6..ee4896c1d2a1 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.m @@ -24,7 +24,7 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen - (void)createWithConfiguration:(WKWebViewConfiguration *)configuration completion:(void (^)(FlutterError *_Nullable))completion { long identifier = [self.instanceManager addHostCreatedInstance:configuration]; - [self createWithIdentifier:@(identifier) completion:completion]; + [self createWithIdentifier:identifier completion:completion]; } @end @@ -72,47 +72,39 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen return self; } -- (WKWebViewConfiguration *)webViewConfigurationForIdentifier:(NSNumber *)identifier { - return (WKWebViewConfiguration *)[self.instanceManager - instanceForIdentifier:identifier.longValue]; +- (WKWebViewConfiguration *)webViewConfigurationForIdentifier:(NSInteger)identifier { + return (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier - error:(FlutterError *_Nullable *_Nonnull)error { +- (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFWebViewConfiguration *webViewConfiguration = [[FWFWebViewConfiguration alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; - [self.instanceManager addDartCreatedInstance:webViewConfiguration - withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:webViewConfiguration withIdentifier:identifier]; } -- (void)createFromWebViewWithIdentifier:(nonnull NSNumber *)identifier - webViewIdentifier:(nonnull NSNumber *)webViewIdentifier +- (void)createFromWebViewWithIdentifier:(NSInteger)identifier + webViewIdentifier:(NSInteger)webViewIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - WKWebView *webView = - (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier.longValue]; - [self.instanceManager addDartCreatedInstance:webView.configuration - withIdentifier:identifier.longValue]; + WKWebView *webView = (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier]; + [self.instanceManager addDartCreatedInstance:webView.configuration withIdentifier:identifier]; } -- (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(nonnull NSNumber *)identifier - isAllowed:(nonnull NSNumber *)allow +- (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(NSInteger)identifier + isAllowed:(BOOL)allow error: (FlutterError *_Nullable *_Nonnull) error { - [[self webViewConfigurationForIdentifier:identifier] - setAllowsInlineMediaPlayback:allow.boolValue]; + [[self webViewConfigurationForIdentifier:identifier] setAllowsInlineMediaPlayback:allow]; } -- (void)setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier: - (nonnull NSNumber *)identifier - isLimited: - (nonnull NSNumber *)limit +- (void)setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier:(NSInteger)identifier + isLimited:(BOOL)limit error:(FlutterError *_Nullable *_Nonnull)error { if (@available(iOS 14, *)) { [[self webViewConfigurationForIdentifier:identifier] - setLimitsNavigationsToAppBoundDomains:limit.boolValue]; + setLimitsNavigationsToAppBoundDomains:limit]; } else { *error = [FlutterError errorWithCode:@"FWFUnsupportedVersionError" @@ -122,7 +114,7 @@ - (void)setLimitsNavigationsToAppBoundDomainsForConfigurationWithIdentifier: } - (void) - setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(nonnull NSNumber *)identifier + setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(NSInteger)identifier forTypes: (nonnull NSArray< FWFWKAudiovisualMediaTypeEnumData diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.m index 92c5b863211e..2c78e91b39d7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.m @@ -95,8 +95,8 @@ - (instancetype)initWithBinaryMessenger:(id)binaryMessen return self; } -- (FWFWebView *)webViewForIdentifier:(NSNumber *)identifier { - return (FWFWebView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (FWFWebView *)webViewForIdentifier:(NSInteger)identifier { + return (FWFWebView *)[self.instanceManager instanceForIdentifier:identifier]; } + (nonnull FlutterError *)errorForURLString:(nonnull NSString *)string { @@ -108,11 +108,11 @@ + (nonnull FlutterError *)errorForURLString:(nonnull NSString *)string { details:errorDetails]; } -- (void)createWithIdentifier:(nonnull NSNumber *)identifier - configurationIdentifier:(nonnull NSNumber *)configurationIdentifier +- (void)createWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager - instanceForIdentifier:configurationIdentifier.longValue]; + instanceForIdentifier:configurationIdentifier]; FWFWebView *webView = [[FWFWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0) configuration:configuration binaryMessenger:self.binaryMessenger @@ -120,11 +120,10 @@ - (void)createWithIdentifier:(nonnull NSNumber *)identifier if (@available(iOS 9.0, *)) { webView.allowsLinkPreview = NO; } - - [self.instanceManager addDartCreatedInstance:webView withIdentifier:identifier.longValue]; + [self.instanceManager addDartCreatedInstance:webView withIdentifier:identifier]; } -- (void)loadRequestForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)loadRequestForWebViewWithIdentifier:(NSInteger)identifier request:(nonnull FWFNSUrlRequestData *)request error: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { @@ -138,7 +137,7 @@ - (void)loadRequestForWebViewWithIdentifier:(nonnull NSNumber *)identifier [[self webViewForIdentifier:identifier] loadRequest:urlRequest]; } -- (void)setCustomUserAgentForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)setCustomUserAgentForWebViewWithIdentifier:(NSInteger)identifier userAgent:(nullable NSString *)userAgent error: (FlutterError *_Nullable __autoreleasing *_Nonnull) @@ -147,31 +146,31 @@ - (void)setCustomUserAgentForWebViewWithIdentifier:(nonnull NSNumber *)identifie } - (nullable NSNumber *) - canGoBackForWebViewWithIdentifier:(nonnull NSNumber *)identifier + canGoBackForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return @([self webViewForIdentifier:identifier].canGoBack); } - (nullable NSString *) - URLForWebViewWithIdentifier:(nonnull NSNumber *)identifier + URLForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return [self webViewForIdentifier:identifier].URL.absoluteString; } - (nullable NSNumber *) - canGoForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier + canGoForwardForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return @([[self webViewForIdentifier:identifier] canGoForward]); } - (nullable NSNumber *) - estimatedProgressForWebViewWithIdentifier:(nonnull NSNumber *)identifier + estimatedProgressForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { return @([[self webViewForIdentifier:identifier] estimatedProgress]); } -- (void)evaluateJavaScriptForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)evaluateJavaScriptForWebViewWithIdentifier:(NSInteger)identifier javaScriptString:(nonnull NSString *)javaScriptString completion: (nonnull void (^)(id _Nullable, @@ -202,12 +201,12 @@ - (void)evaluateJavaScriptForWebViewWithIdentifier:(nonnull NSNumber *)identifie }]; } -- (void)setInspectableForWebViewWithIdentifier:(NSNumber *)identifier - inspectable:(NSNumber *)inspectable +- (void)setInspectableForWebViewWithIdentifier:(NSInteger)identifier + inspectable:(BOOL)inspectable error:(FlutterError *_Nullable *_Nonnull)error { if (@available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130300 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400 - [[self webViewForIdentifier:identifier] setInspectable:inspectable.boolValue]; + [[self webViewForIdentifier:identifier] setInspectable:inspectable]; #endif } else { *error = [FlutterError errorWithCode:@"FWFUnsupportedVersionError" @@ -216,17 +215,17 @@ - (void)setInspectableForWebViewWithIdentifier:(NSNumber *)identifier } } -- (void)goBackForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)goBackForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] goBack]; } -- (void)goForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)goForwardForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] goForward]; } -- (void)loadAssetForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)loadAssetForWebViewWithIdentifier:(NSInteger)identifier assetKey:(nonnull NSString *)key error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { NSString *assetFilePath = [self.assetManager lookupKeyForAsset:key]; @@ -241,7 +240,7 @@ - (void)loadAssetForWebViewWithIdentifier:(nonnull NSNumber *)identifier } } -- (void)loadFileForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)loadFileForWebViewWithIdentifier:(NSInteger)identifier fileURL:(nonnull NSString *)url readAccessURL:(nonnull NSString *)readAccessUrl error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { @@ -258,7 +257,7 @@ - (void)loadFileForWebViewWithIdentifier:(nonnull NSNumber *)identifier } } -- (void)loadHTMLForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)loadHTMLForWebViewWithIdentifier:(NSInteger)identifier HTMLString:(nonnull NSString *)string baseURL:(nullable NSString *)baseUrl error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { @@ -266,21 +265,21 @@ - (void)loadHTMLForWebViewWithIdentifier:(nonnull NSNumber *)identifier baseURL:[NSURL URLWithString:baseUrl]]; } -- (void)reloadWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)reloadWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] reload]; } - (void) - setAllowsBackForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier - isAllowed:(nonnull NSNumber *)allow + setAllowsBackForwardForWebViewWithIdentifier:(NSInteger)identifier + isAllowed:(BOOL)allow error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { - [[self webViewForIdentifier:identifier] setAllowsBackForwardNavigationGestures:allow.boolValue]; + [[self webViewForIdentifier:identifier] setAllowsBackForwardNavigationGestures:allow]; } - (void) - setNavigationDelegateForWebViewWithIdentifier:(nonnull NSNumber *)identifier + setNavigationDelegateForWebViewWithIdentifier:(NSInteger)identifier delegateIdentifier:(nullable NSNumber *)navigationDelegateIdentifier error: (FlutterError *_Nullable __autoreleasing *_Nonnull) @@ -290,7 +289,7 @@ - (void)reloadWebViewWithIdentifier:(nonnull NSNumber *)identifier [[self webViewForIdentifier:identifier] setNavigationDelegate:navigationDelegate]; } -- (void)setUIDelegateForWebViewWithIdentifier:(nonnull NSNumber *)identifier +- (void)setUIDelegateForWebViewWithIdentifier:(NSInteger)identifier delegateIdentifier:(nullable NSNumber *)uiDelegateIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { @@ -300,13 +299,13 @@ - (void)setUIDelegateForWebViewWithIdentifier:(nonnull NSNumber *)identifier } - (nullable NSString *) - titleForWebViewWithIdentifier:(nonnull NSNumber *)identifier + titleForWebViewWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return [[self webViewForIdentifier:identifier] title]; } - (nullable NSString *) - customUserAgentForWebViewWithIdentifier:(nonnull NSNumber *)identifier + customUserAgentForWebViewWithIdentifier:(NSInteger)identifier error: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { return [[self webViewForIdentifier:identifier] customUserAgent]; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.m index 51c7784931bb..f7252240957d 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.m @@ -20,49 +20,47 @@ - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { return self; } -- (WKWebsiteDataStore *)websiteDataStoreForIdentifier:(NSNumber *)identifier { - return (WKWebsiteDataStore *)[self.instanceManager instanceForIdentifier:identifier.longValue]; +- (WKWebsiteDataStore *)websiteDataStoreForIdentifier:(NSInteger)identifier { + return (WKWebsiteDataStore *)[self.instanceManager instanceForIdentifier:identifier]; } -- (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier - configurationIdentifier:(nonnull NSNumber *)configurationIdentifier +- (void)createFromWebViewConfigurationWithIdentifier:(NSInteger)identifier + configurationIdentifier:(NSInteger)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager - instanceForIdentifier:configurationIdentifier.longValue]; + instanceForIdentifier:configurationIdentifier]; [self.instanceManager addDartCreatedInstance:configuration.websiteDataStore - withIdentifier:identifier.longValue]; + withIdentifier:identifier]; } -- (void)createDefaultDataStoreWithIdentifier:(nonnull NSNumber *)identifier +- (void)createDefaultDataStoreWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { [self.instanceManager addDartCreatedInstance:[WKWebsiteDataStore defaultDataStore] - withIdentifier:identifier.longValue]; + withIdentifier:identifier]; } -- (void) - removeDataFromDataStoreWithIdentifier:(nonnull NSNumber *)identifier - ofTypes: - (nonnull NSArray *)dataTypes - modifiedSince:(nonnull NSNumber *)modificationTimeInSecondsSinceEpoch - completion:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { +- (void)removeDataFromDataStoreWithIdentifier:(NSInteger)identifier + ofTypes:(nonnull NSArray *) + dataTypes + modifiedSince:(double)modificationTimeInSecondsSinceEpoch + completion: + (nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { NSMutableSet *stringDataTypes = [NSMutableSet set]; for (FWFWKWebsiteDataTypeEnumData *type in dataTypes) { [stringDataTypes addObject:FWFNativeWKWebsiteDataTypeFromEnumData(type)]; } WKWebsiteDataStore *dataStore = [self websiteDataStoreForIdentifier:identifier]; - [dataStore - fetchDataRecordsOfTypes:stringDataTypes - completionHandler:^(NSArray *records) { - [dataStore - removeDataOfTypes:stringDataTypes - modifiedSince:[NSDate dateWithTimeIntervalSince1970: - modificationTimeInSecondsSinceEpoch.doubleValue] - completionHandler:^{ - completion([NSNumber numberWithBool:(records.count > 0)], nil); - }]; - }]; + [dataStore fetchDataRecordsOfTypes:stringDataTypes + completionHandler:^(NSArray *records) { + [dataStore removeDataOfTypes:stringDataTypes + modifiedSince:[NSDate dateWithTimeIntervalSince1970: + modificationTimeInSecondsSinceEpoch] + completionHandler:^{ + completion([NSNumber numberWithBool:(records.count > 0)], nil); + }]; + }]; } @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 b0eaadeb739f..ff08766ba90f 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 (v10.1.4), 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,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + /// Mirror of NSKeyValueObservingOptions. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc. @@ -1149,8 +1160,15 @@ abstract class WKWebViewConfigurationFlutterApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationFlutterApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1501,9 +1519,16 @@ abstract class WKScriptMessageHandlerFlutterApi { (args[2] as WKScriptMessageData?); assert(arg_message != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage was null, expected non-null WKScriptMessageData.'); - api.didReceiveScriptMessage(arg_identifier!, - arg_userContentControllerIdentifier!, arg_message!); - return; + try { + api.didReceiveScriptMessage(arg_identifier!, + arg_userContentControllerIdentifier!, arg_message!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1637,9 +1662,16 @@ abstract class WKNavigationDelegateFlutterApi { assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didFinishNavigation was null, expected non-null int.'); final String? arg_url = (args[2] as String?); - api.didFinishNavigation( - arg_identifier!, arg_webViewIdentifier!, arg_url); - return; + try { + api.didFinishNavigation( + arg_identifier!, arg_webViewIdentifier!, arg_url); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1662,9 +1694,16 @@ abstract class WKNavigationDelegateFlutterApi { assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation was null, expected non-null int.'); final String? arg_url = (args[2] as String?); - api.didStartProvisionalNavigation( - arg_identifier!, arg_webViewIdentifier!, arg_url); - return; + try { + api.didStartProvisionalNavigation( + arg_identifier!, arg_webViewIdentifier!, arg_url); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1690,10 +1729,17 @@ abstract class WKNavigationDelegateFlutterApi { (args[2] as WKNavigationActionData?); assert(arg_navigationAction != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction was null, expected non-null WKNavigationActionData.'); - final WKNavigationActionPolicyEnumData output = - await api.decidePolicyForNavigationAction(arg_identifier!, - arg_webViewIdentifier!, arg_navigationAction!); - return output; + try { + final WKNavigationActionPolicyEnumData output = + await api.decidePolicyForNavigationAction(arg_identifier!, + arg_webViewIdentifier!, arg_navigationAction!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1718,9 +1764,16 @@ abstract class WKNavigationDelegateFlutterApi { final NSErrorData? arg_error = (args[2] as NSErrorData?); assert(arg_error != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didFailNavigation was null, expected non-null NSErrorData.'); - api.didFailNavigation( - arg_identifier!, arg_webViewIdentifier!, arg_error!); - return; + try { + api.didFailNavigation( + arg_identifier!, arg_webViewIdentifier!, arg_error!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1745,9 +1798,16 @@ abstract class WKNavigationDelegateFlutterApi { final NSErrorData? arg_error = (args[2] as NSErrorData?); assert(arg_error != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation was null, expected non-null NSErrorData.'); - api.didFailProvisionalNavigation( - arg_identifier!, arg_webViewIdentifier!, arg_error!); - return; + try { + api.didFailProvisionalNavigation( + arg_identifier!, arg_webViewIdentifier!, arg_error!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1769,9 +1829,16 @@ abstract class WKNavigationDelegateFlutterApi { final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate was null, expected non-null int.'); - api.webViewWebContentProcessDidTerminate( - arg_identifier!, arg_webViewIdentifier!); - return; + try { + api.webViewWebContentProcessDidTerminate( + arg_identifier!, arg_webViewIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1968,9 +2035,16 @@ abstract class NSObjectFlutterApi { (args[4] as List?)?.cast(); assert(arg_changeValues != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.observeValue was null, expected non-null List.'); - api.observeValue(arg_identifier!, arg_keyPath!, arg_objectIdentifier!, - arg_changeKeys!, arg_changeValues!); - return; + try { + api.observeValue(arg_identifier!, arg_keyPath!, + arg_objectIdentifier!, arg_changeKeys!, arg_changeValues!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1989,8 +2063,15 @@ abstract class NSObjectFlutterApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_identifier!); - return; + try { + api.dispose(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -2738,9 +2819,16 @@ abstract class WKUIDelegateFlutterApi { (args[3] as WKNavigationActionData?); assert(arg_navigationAction != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.onCreateWebView was null, expected non-null WKNavigationActionData.'); - api.onCreateWebView(arg_identifier!, arg_webViewIdentifier!, - arg_configurationIdentifier!, arg_navigationAction!); - return; + try { + api.onCreateWebView(arg_identifier!, arg_webViewIdentifier!, + arg_configurationIdentifier!, arg_navigationAction!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -2773,10 +2861,17 @@ abstract class WKUIDelegateFlutterApi { (args[4] as WKMediaCaptureTypeData?); assert(arg_type != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.requestMediaCapturePermission was null, expected non-null WKMediaCaptureTypeData.'); - final WKPermissionDecisionData output = - await api.requestMediaCapturePermission(arg_identifier!, - arg_webViewIdentifier!, arg_origin!, arg_frame!, arg_type!); - return output; + try { + final WKPermissionDecisionData output = + await api.requestMediaCapturePermission(arg_identifier!, + arg_webViewIdentifier!, arg_origin!, arg_frame!, arg_type!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -2943,8 +3038,15 @@ abstract class NSUrlFlutterApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlFlutterApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } 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 a02ed61069e7..d15b28251828 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 @@ -70,8 +70,14 @@ class WebKitWebViewControllerCreationParams ); } _configuration.setAllowsInlineMediaPlayback(allowsInlineMediaPlayback); - _configuration.setLimitsNavigationsToAppBoundDomains( - limitsNavigationsToAppBoundDomains); + // `WKWebViewConfiguration.limitsNavigationsToAppBoundDomains` is only + // supported on iOS versions 14+. So this only calls it if the value is set + // to true. + if (limitsNavigationsToAppBoundDomains) { + _configuration.setLimitsNavigationsToAppBoundDomains( + limitsNavigationsToAppBoundDomains, + ); + } } /// Constructs a [WebKitWebViewControllerCreationParams] using a diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 63ed6e4db5f0..035fecbf2cfa 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.9.1 +version: 3.9.4 environment: sdk: ">=2.19.0 <4.0.0" @@ -27,7 +27,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^10.1.4 + pigeon: ^13.0.0 topics: - html 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 03e5b6cbca5e..1541f02adca7 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 (v10.1.4), 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 @@ -78,9 +78,16 @@ abstract class TestWKWebsiteDataStoreHostApi { final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration was null, expected non-null int.'); - api.createFromWebViewConfiguration( - arg_identifier!, arg_configurationIdentifier!); - return []; + try { + api.createFromWebViewConfiguration( + arg_identifier!, arg_configurationIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -102,8 +109,15 @@ abstract class TestWKWebsiteDataStoreHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.createDefaultDataStore was null, expected non-null int.'); - api.createDefaultDataStore(arg_identifier!); - return []; + try { + api.createDefaultDataStore(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -133,9 +147,16 @@ abstract class TestWKWebsiteDataStoreHostApi { (args[2] as double?); assert(arg_modificationTimeInSecondsSinceEpoch != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebsiteDataStoreHostApi.removeDataOfTypes was null, expected non-null double.'); - final bool output = await api.removeDataOfTypes(arg_identifier!, - arg_dataTypes!, arg_modificationTimeInSecondsSinceEpoch!); - return [output]; + try { + final bool output = await api.removeDataOfTypes(arg_identifier!, + arg_dataTypes!, arg_modificationTimeInSecondsSinceEpoch!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -175,8 +196,15 @@ abstract class TestUIViewHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setBackgroundColor was null, expected non-null int.'); final int? arg_value = (args[1] as int?); - api.setBackgroundColor(arg_identifier!, arg_value); - return []; + try { + api.setBackgroundColor(arg_identifier!, arg_value); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -201,8 +229,15 @@ abstract class TestUIViewHostApi { final bool? arg_opaque = (args[1] as bool?); assert(arg_opaque != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIViewHostApi.setOpaque was null, expected non-null bool.'); - api.setOpaque(arg_identifier!, arg_opaque!); - return []; + try { + api.setOpaque(arg_identifier!, arg_opaque!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -248,8 +283,15 @@ abstract class TestUIScrollViewHostApi { final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.createFromWebView was null, expected non-null int.'); - api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); - return []; + try { + api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -271,8 +313,15 @@ abstract class TestUIScrollViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.getContentOffset was null, expected non-null int.'); - final List output = api.getContentOffset(arg_identifier!); - return [output]; + try { + final List output = api.getContentOffset(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -300,8 +349,15 @@ abstract class TestUIScrollViewHostApi { final double? arg_y = (args[2] as double?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.scrollBy was null, expected non-null double.'); - api.scrollBy(arg_identifier!, arg_x!, arg_y!); - return []; + try { + api.scrollBy(arg_identifier!, arg_x!, arg_y!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -329,8 +385,15 @@ abstract class TestUIScrollViewHostApi { final double? arg_y = (args[2] as double?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.UIScrollViewHostApi.setContentOffset was null, expected non-null double.'); - api.setContentOffset(arg_identifier!, arg_x!, arg_y!); - return []; + try { + api.setContentOffset(arg_identifier!, arg_x!, arg_y!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -400,8 +463,15 @@ abstract class TestWKWebViewConfigurationHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return []; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -426,8 +496,15 @@ abstract class TestWKWebViewConfigurationHostApi { final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.createFromWebView was null, expected non-null int.'); - api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); - return []; + try { + api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -452,8 +529,15 @@ abstract class TestWKWebViewConfigurationHostApi { final bool? arg_allow = (args[1] as bool?); assert(arg_allow != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback was null, expected non-null bool.'); - api.setAllowsInlineMediaPlayback(arg_identifier!, arg_allow!); - return []; + try { + api.setAllowsInlineMediaPlayback(arg_identifier!, arg_allow!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -478,9 +562,16 @@ abstract class TestWKWebViewConfigurationHostApi { final bool? arg_limit = (args[1] as bool?); assert(arg_limit != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setLimitsNavigationsToAppBoundDomains was null, expected non-null bool.'); - api.setLimitsNavigationsToAppBoundDomains( - arg_identifier!, arg_limit!); - return []; + try { + api.setLimitsNavigationsToAppBoundDomains( + arg_identifier!, arg_limit!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -507,9 +598,16 @@ abstract class TestWKWebViewConfigurationHostApi { ?.cast(); assert(arg_types != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback was null, expected non-null List.'); - api.setMediaTypesRequiringUserActionForPlayback( - arg_identifier!, arg_types!); - return []; + try { + api.setMediaTypesRequiringUserActionForPlayback( + arg_identifier!, arg_types!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -590,9 +688,16 @@ abstract class TestWKUserContentControllerHostApi { final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.createFromWebViewConfiguration was null, expected non-null int.'); - api.createFromWebViewConfiguration( - arg_identifier!, arg_configurationIdentifier!); - return []; + try { + api.createFromWebViewConfiguration( + arg_identifier!, arg_configurationIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -620,9 +725,16 @@ abstract class TestWKUserContentControllerHostApi { final String? arg_name = (args[2] as String?); assert(arg_name != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addScriptMessageHandler was null, expected non-null String.'); - api.addScriptMessageHandler( - arg_identifier!, arg_handlerIdentifier!, arg_name!); - return []; + try { + api.addScriptMessageHandler( + arg_identifier!, arg_handlerIdentifier!, arg_name!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -647,8 +759,15 @@ abstract class TestWKUserContentControllerHostApi { final String? arg_name = (args[1] as String?); assert(arg_name != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeScriptMessageHandler was null, expected non-null String.'); - api.removeScriptMessageHandler(arg_identifier!, arg_name!); - return []; + try { + api.removeScriptMessageHandler(arg_identifier!, arg_name!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -670,8 +789,15 @@ abstract class TestWKUserContentControllerHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllScriptMessageHandlers was null, expected non-null int.'); - api.removeAllScriptMessageHandlers(arg_identifier!); - return []; + try { + api.removeAllScriptMessageHandlers(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -697,8 +823,15 @@ abstract class TestWKUserContentControllerHostApi { (args[1] as WKUserScriptData?); assert(arg_userScript != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.addUserScript was null, expected non-null WKUserScriptData.'); - api.addUserScript(arg_identifier!, arg_userScript!); - return []; + try { + api.addUserScript(arg_identifier!, arg_userScript!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -720,8 +853,15 @@ abstract class TestWKUserContentControllerHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUserContentControllerHostApi.removeAllUserScripts was null, expected non-null int.'); - api.removeAllUserScripts(arg_identifier!); - return []; + try { + api.removeAllUserScripts(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -764,9 +904,16 @@ abstract class TestWKPreferencesHostApi { final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.createFromWebViewConfiguration was null, expected non-null int.'); - api.createFromWebViewConfiguration( - arg_identifier!, arg_configurationIdentifier!); - return []; + try { + api.createFromWebViewConfiguration( + arg_identifier!, arg_configurationIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -791,8 +938,15 @@ abstract class TestWKPreferencesHostApi { final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKPreferencesHostApi.setJavaScriptEnabled was null, expected non-null bool.'); - api.setJavaScriptEnabled(arg_identifier!, arg_enabled!); - return []; + try { + api.setJavaScriptEnabled(arg_identifier!, arg_enabled!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -829,8 +983,15 @@ abstract class TestWKScriptMessageHandlerHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKScriptMessageHandlerHostApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return []; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -867,8 +1028,15 @@ abstract class TestWKNavigationDelegateHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateHostApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return []; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -933,8 +1101,15 @@ abstract class TestNSObjectHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.dispose was null, expected non-null int.'); - api.dispose(arg_identifier!); - return []; + try { + api.dispose(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -967,9 +1142,16 @@ abstract class TestNSObjectHostApi { ?.cast(); assert(arg_options != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.addObserver was null, expected non-null List.'); - api.addObserver(arg_identifier!, arg_observerIdentifier!, - arg_keyPath!, arg_options!); - return []; + try { + api.addObserver(arg_identifier!, arg_observerIdentifier!, + arg_keyPath!, arg_options!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -997,9 +1179,16 @@ abstract class TestNSObjectHostApi { final String? arg_keyPath = (args[2] as String?); assert(arg_keyPath != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSObjectHostApi.removeObserver was null, expected non-null String.'); - api.removeObserver( - arg_identifier!, arg_observerIdentifier!, arg_keyPath!); - return []; + try { + api.removeObserver( + arg_identifier!, arg_observerIdentifier!, arg_keyPath!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1185,8 +1374,15 @@ abstract class TestWKWebViewHostApi { final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.create was null, expected non-null int.'); - api.create(arg_identifier!, arg_configurationIdentifier!); - return []; + try { + api.create(arg_identifier!, arg_configurationIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1209,8 +1405,15 @@ abstract class TestWKWebViewHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setUIDelegate was null, expected non-null int.'); final int? arg_uiDelegateIdentifier = (args[1] as int?); - api.setUIDelegate(arg_identifier!, arg_uiDelegateIdentifier); - return []; + try { + api.setUIDelegate(arg_identifier!, arg_uiDelegateIdentifier); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1233,9 +1436,16 @@ abstract class TestWKWebViewHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setNavigationDelegate was null, expected non-null int.'); final int? arg_navigationDelegateIdentifier = (args[1] as int?); - api.setNavigationDelegate( - arg_identifier!, arg_navigationDelegateIdentifier); - return []; + try { + api.setNavigationDelegate( + arg_identifier!, arg_navigationDelegateIdentifier); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1257,8 +1467,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getUrl was null, expected non-null int.'); - final String? output = api.getUrl(arg_identifier!); - return [output]; + try { + final String? output = api.getUrl(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1280,8 +1497,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getEstimatedProgress was null, expected non-null int.'); - final double output = api.getEstimatedProgress(arg_identifier!); - return [output]; + try { + final double output = api.getEstimatedProgress(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1306,8 +1530,15 @@ abstract class TestWKWebViewHostApi { final NSUrlRequestData? arg_request = (args[1] as NSUrlRequestData?); assert(arg_request != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadRequest was null, expected non-null NSUrlRequestData.'); - api.loadRequest(arg_identifier!, arg_request!); - return []; + try { + api.loadRequest(arg_identifier!, arg_request!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1333,8 +1564,15 @@ abstract class TestWKWebViewHostApi { assert(arg_string != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadHtmlString was null, expected non-null String.'); final String? arg_baseUrl = (args[2] as String?); - api.loadHtmlString(arg_identifier!, arg_string!, arg_baseUrl); - return []; + try { + api.loadHtmlString(arg_identifier!, arg_string!, arg_baseUrl); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1362,8 +1600,15 @@ abstract class TestWKWebViewHostApi { final String? arg_readAccessUrl = (args[2] as String?); assert(arg_readAccessUrl != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFileUrl was null, expected non-null String.'); - api.loadFileUrl(arg_identifier!, arg_url!, arg_readAccessUrl!); - return []; + try { + api.loadFileUrl(arg_identifier!, arg_url!, arg_readAccessUrl!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1388,8 +1633,15 @@ abstract class TestWKWebViewHostApi { final String? arg_key = (args[1] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.loadFlutterAsset was null, expected non-null String.'); - api.loadFlutterAsset(arg_identifier!, arg_key!); - return []; + try { + api.loadFlutterAsset(arg_identifier!, arg_key!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1411,8 +1663,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoBack was null, expected non-null int.'); - final bool output = api.canGoBack(arg_identifier!); - return [output]; + try { + final bool output = api.canGoBack(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1434,8 +1693,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.canGoForward was null, expected non-null int.'); - final bool output = api.canGoForward(arg_identifier!); - return [output]; + try { + final bool output = api.canGoForward(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1457,8 +1723,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goBack was null, expected non-null int.'); - api.goBack(arg_identifier!); - return []; + try { + api.goBack(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1480,8 +1753,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.goForward was null, expected non-null int.'); - api.goForward(arg_identifier!); - return []; + try { + api.goForward(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1503,8 +1783,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.reload was null, expected non-null int.'); - api.reload(arg_identifier!); - return []; + try { + api.reload(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1526,8 +1813,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getTitle was null, expected non-null int.'); - final String? output = api.getTitle(arg_identifier!); - return [output]; + try { + final String? output = api.getTitle(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1552,9 +1846,16 @@ abstract class TestWKWebViewHostApi { final bool? arg_allow = (args[1] as bool?); assert(arg_allow != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setAllowsBackForwardNavigationGestures was null, expected non-null bool.'); - api.setAllowsBackForwardNavigationGestures( - arg_identifier!, arg_allow!); - return []; + try { + api.setAllowsBackForwardNavigationGestures( + arg_identifier!, arg_allow!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1577,8 +1878,15 @@ abstract class TestWKWebViewHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setCustomUserAgent was null, expected non-null int.'); final String? arg_userAgent = (args[1] as String?); - api.setCustomUserAgent(arg_identifier!, arg_userAgent); - return []; + try { + api.setCustomUserAgent(arg_identifier!, arg_userAgent); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1603,9 +1911,16 @@ abstract class TestWKWebViewHostApi { final String? arg_javaScriptString = (args[1] as String?); assert(arg_javaScriptString != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.evaluateJavaScript was null, expected non-null String.'); - final Object? output = await api.evaluateJavaScript( - arg_identifier!, arg_javaScriptString!); - return [output]; + try { + final Object? output = await api.evaluateJavaScript( + arg_identifier!, arg_javaScriptString!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1630,8 +1945,15 @@ abstract class TestWKWebViewHostApi { final bool? arg_inspectable = (args[1] as bool?); assert(arg_inspectable != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.setInspectable was null, expected non-null bool.'); - api.setInspectable(arg_identifier!, arg_inspectable!); - return []; + try { + api.setInspectable(arg_identifier!, arg_inspectable!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1653,8 +1975,15 @@ abstract class TestWKWebViewHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKWebViewHostApi.getCustomUserAgent was null, expected non-null int.'); - final String? output = api.getCustomUserAgent(arg_identifier!); - return [output]; + try { + final String? output = api.getCustomUserAgent(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1691,8 +2020,15 @@ abstract class TestWKUIDelegateHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateHostApi.create was null, expected non-null int.'); - api.create(arg_identifier!); - return []; + try { + api.create(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1764,9 +2100,16 @@ abstract class TestWKHttpCookieStoreHostApi { final int? arg_websiteDataStoreIdentifier = (args[1] as int?); assert(arg_websiteDataStoreIdentifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.createFromWebsiteDataStore was null, expected non-null int.'); - api.createFromWebsiteDataStore( - arg_identifier!, arg_websiteDataStoreIdentifier!); - return []; + try { + api.createFromWebsiteDataStore( + arg_identifier!, arg_websiteDataStoreIdentifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1791,8 +2134,15 @@ abstract class TestWKHttpCookieStoreHostApi { final NSHttpCookieData? arg_cookie = (args[1] as NSHttpCookieData?); assert(arg_cookie != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKHttpCookieStoreHostApi.setCookie was null, expected non-null NSHttpCookieData.'); - await api.setCookie(arg_identifier!, arg_cookie!); - return []; + try { + await api.setCookie(arg_identifier!, arg_cookie!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } @@ -1832,8 +2182,15 @@ abstract class TestNSUrlHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlHostApi.getAbsoluteString was null, expected non-null int.'); - final String? output = api.getAbsoluteString(arg_identifier!); - return [output]; + try { + final String? output = api.getAbsoluteString(arg_identifier!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } 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 d1854df4bce0..46badc069058 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 @@ -160,6 +160,25 @@ void main() { ); }); + test( + 'limitsNavigationsToAppBoundDomains is not called if it uses default value (false)', + () { + final MockWKWebViewConfiguration mockConfiguration = + MockWKWebViewConfiguration(); + + WebKitWebViewControllerCreationParams( + webKitProxy: WebKitProxy( + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, + ), + ); + + verifyNever( + mockConfiguration.setLimitsNavigationsToAppBoundDomains(any), + ); + }); + test('mediaTypesRequiringUserAction', () { final MockWKWebViewConfiguration mockConfiguration = MockWKWebViewConfiguration(); diff --git a/script/configs/still_requires_api_33_avd.yaml b/script/configs/still_requires_api_33_avd.yaml index 87f711c25b77..9db9ab0161c9 100644 --- a/script/configs/still_requires_api_33_avd.yaml +++ b/script/configs/still_requires_api_33_avd.yaml @@ -1,4 +1,4 @@ # Running the somes tests from these packages on an AVD with Android 34 causes failures. -- file_selector - quick_actions - webview_flutter +- path_provider diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart index 345ace50e5ca..a32424e30cfe 100644 --- a/script/tool/lib/src/common/repository_package.dart +++ b/script/tool/lib/src/common/repository_package.dart @@ -142,6 +142,18 @@ class RepositoryPackage { !isPlatformInterface && directory.basename != directory.parent.basename; + /// True if this appears to be an example package, according to package + /// conventions. + bool get isExample { + final RepositoryPackage? enclosingPackage = getEnclosingPackage(); + if (enclosingPackage == null) { + // An example package is enclosed in another package. + return false; + } + // Check whether this is one of the enclosing package's examples. + return enclosingPackage.getExamples().any((RepositoryPackage p) => p.path == path); + } + /// Returns the Flutter example packages contained in the package, if any. Iterable getExamples() { final Directory exampleDirectory = directory.childDirectory('example'); diff --git a/script/tool/lib/src/update_dependency_command.dart b/script/tool/lib/src/update_dependency_command.dart index 185abb57fad6..548465c5f338 100644 --- a/script/tool/lib/src/update_dependency_command.dart +++ b/script/tool/lib/src/update_dependency_command.dart @@ -42,10 +42,17 @@ class UpdateDependencyCommand extends PackageLoopingCommand { argParser.addOption(_androidDependency, help: 'An Android dependency to update.', allowed: [ - 'gradle', + _AndroidDepdencyType.gradle, + _AndroidDepdencyType.compileSdk, + _AndroidDepdencyType.compileSdkForExamples, ], allowedHelp: { - 'gradle': 'Updates Gradle version used in plugin example apps.', + _AndroidDepdencyType.gradle: + 'Updates Gradle version used in plugin example apps.', + _AndroidDepdencyType.compileSdk: + 'Updates compileSdk version used to compile plugins.', + _AndroidDepdencyType.compileSdkForExamples: + 'Updates compileSdk version used to compile plugin examples.', }); argParser.addOption( _versionFlag, @@ -130,7 +137,7 @@ ${response.httpResponse.body} if (version == null) { printError('A version must be provided to update this dependency.'); throw ToolExit(_exitNoTargetVersion); - } else if (_targetAndroidDependency == 'gradle') { + } else if (_targetAndroidDependency == _AndroidDepdencyType.gradle) { final RegExp validGradleVersionPattern = RegExp(r'^\d+(?:\.\d+){1,2}$'); final bool isValidGradleVersion = validGradleVersionPattern.stringMatch(version) == version; @@ -139,14 +146,24 @@ ${response.httpResponse.body} 'A version with a valid format (maximum 2-3 numbers separated by period) must be provided.'); throw ToolExit(_exitInvalidTargetVersion); } - _targetVersion = version; - return; + } else if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk || + _targetAndroidDependency == + _AndroidDepdencyType.compileSdkForExamples) { + final RegExp validSdkVersion = RegExp(r'^\d{1,2}$'); + final bool isValidSdkVersion = + validSdkVersion.stringMatch(version) == version; + if (!isValidSdkVersion) { + printError( + 'A valid Android SDK version number (1-2 digit numbers) must be provided.'); + throw ToolExit(_exitInvalidTargetVersion); + } } else { - // TODO(camsim99): Add other supported Android dependencies like the Android SDK and AGP. + // TODO(camsim99): Add other supported Android dependencies like the min/target Android SDK and AGP. printError( 'Target Android dependency $_targetAndroidDependency is unrecognized.'); throw ToolExit(_exitIncorrectTargetDependency); } + _targetVersion = version; } } @@ -233,59 +250,114 @@ ${response.httpResponse.body} /// an Android dependency. Future _runForAndroidDependency( RepositoryPackage package) async { - if (_targetAndroidDependency == 'gradle') { - final Iterable packageExamples = package.getExamples(); - bool updateRanForExamples = false; - for (final RepositoryPackage example in packageExamples) { - if (!example.platformDirectory(FlutterPlatform.android).existsSync()) { - continue; - } + if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk) { + return _runForCompileSdkVersion(package); + } else if (_targetAndroidDependency == _AndroidDepdencyType.gradle || + _targetAndroidDependency == + _AndroidDepdencyType.compileSdkForExamples) { + return _runForAndroidDependencyOnExamples(package); + } - updateRanForExamples = true; - Directory gradleWrapperPropertiesDirectory = - example.platformDirectory(FlutterPlatform.android); - if (gradleWrapperPropertiesDirectory + return PackageResult.fail([ + 'Target Android dependency $_androidDependency is unrecognized.' + ]); + } + + Future _runForAndroidDependencyOnExamples( + RepositoryPackage package) async { + final Iterable packageExamples = package.getExamples(); + bool updateRanForExamples = false; + for (final RepositoryPackage example in packageExamples) { + if (!example.platformDirectory(FlutterPlatform.android).existsSync()) { + continue; + } + + updateRanForExamples = true; + Directory androidDirectory = + example.platformDirectory(FlutterPlatform.android); + final File fileToUpdate; + final RegExp dependencyVersionPattern; + final String newDependencyVersionEntry; + + if (_targetAndroidDependency == _AndroidDepdencyType.gradle) { + if (androidDirectory .childDirectory('app') .childDirectory('gradle') .existsSync()) { - gradleWrapperPropertiesDirectory = - gradleWrapperPropertiesDirectory.childDirectory('app'); + androidDirectory = androidDirectory.childDirectory('app'); } - final File gradleWrapperPropertiesFile = - gradleWrapperPropertiesDirectory - .childDirectory('gradle') - .childDirectory('wrapper') - .childFile('gradle-wrapper.properties'); - - final String gradleWrapperPropertiesContents = - gradleWrapperPropertiesFile.readAsStringSync(); - final RegExp validGradleDistributionUrl = + fileToUpdate = androidDirectory + .childDirectory('gradle') + .childDirectory('wrapper') + .childFile('gradle-wrapper.properties'); + dependencyVersionPattern = RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true); - if (!validGradleDistributionUrl - .hasMatch(gradleWrapperPropertiesContents)) { - return PackageResult.fail([ - 'Unable to find a "distributionUrl" entry to update for ${package.displayName}.' - ]); - } - - print( - '${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"'); - final String newGradleWrapperPropertiesContents = - gradleWrapperPropertiesContents.replaceFirst( - validGradleDistributionUrl, - 'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip'); // TODO(camsim99): Validate current AGP version against target Gradle // version: https://github.com/flutter/flutter/issues/133887. - gradleWrapperPropertiesFile - .writeAsStringSync(newGradleWrapperPropertiesContents); + newDependencyVersionEntry = + 'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip'; + } else if (_targetAndroidDependency == + _AndroidDepdencyType.compileSdkForExamples) { + fileToUpdate = + androidDirectory.childDirectory('app').childFile('build.gradle'); + dependencyVersionPattern = RegExp( + r'(compileSdk|compileSdkVersion) (\d{1,2}|flutter.compileSdkVersion)'); + newDependencyVersionEntry = 'compileSdk $_targetVersion'; + } else { + printError( + 'Target Android dependency $_targetAndroidDependency is unrecognized.'); + throw ToolExit(_exitIncorrectTargetDependency); + } + + final String oldFileToUpdateContents = fileToUpdate.readAsStringSync(); + + if (!dependencyVersionPattern.hasMatch(oldFileToUpdateContents)) { + return PackageResult.fail([ + 'Unable to find a $_targetAndroidDependency version entry to update for ${example.displayName}.' + ]); } - return updateRanForExamples - ? PackageResult.success() - : PackageResult.skip('No example apps run on Android.'); + + print( + '${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"'); + final String newGradleWrapperPropertiesContents = oldFileToUpdateContents + .replaceFirst(dependencyVersionPattern, newDependencyVersionEntry); + + fileToUpdate.writeAsStringSync(newGradleWrapperPropertiesContents); } - return PackageResult.fail([ - 'Target Android dependency $_androidDependency is unrecognized.' - ]); + return updateRanForExamples + ? PackageResult.success() + : PackageResult.skip('No example apps run on Android.'); + } + + Future _runForCompileSdkVersion( + RepositoryPackage package) async { + if (!package.platformDirectory(FlutterPlatform.android).existsSync()) { + return PackageResult.skip( + 'Package ${package.displayName} does not run on Android.'); + } else if (package.isExample) { + // We skip examples for this command. + return PackageResult.skip( + 'Package ${package.displayName} is not a top-level package; run with "compileSdkForExamples" to update.'); + } + final File buildConfigurationFile = package + .platformDirectory(FlutterPlatform.android) + .childFile('build.gradle'); + final String buildConfigurationContents = + buildConfigurationFile.readAsStringSync(); + final RegExp validCompileSdkVersion = + RegExp(r'(compileSdk|compileSdkVersion) \d{1,2}'); + + if (!validCompileSdkVersion.hasMatch(buildConfigurationContents)) { + return PackageResult.fail([ + 'Unable to find a compileSdk version entry to update for ${package.displayName}.' + ]); + } + print('${indentation}Updating ${package.directory} to "$_targetVersion"'); + final String newBuildConfigurationContents = buildConfigurationContents + .replaceFirst(validCompileSdkVersion, 'compileSdk $_targetVersion'); + buildConfigurationFile.writeAsStringSync(newBuildConfigurationContents); + + return PackageResult.success(); } /// Returns information about the current dependency of [package] on @@ -414,3 +486,9 @@ class _PubDependencyInfo { } enum _PubDependencyType { normal, dev } + +class _AndroidDepdencyType { + static const String gradle = 'gradle'; + static const String compileSdk = 'compileSdk'; + static const String compileSdkForExamples = 'compileSdkForExamples'; +} diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index c27a1ad33c44..81fa3eec5100 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -558,11 +558,12 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. } else { printError( 'No version change found, but the change to this package could ' - 'not be verified to be exempt from version changes according to ' - 'repository policy. If this is a false positive, please comment in ' - 'the PR to explain why the PR is exempt, and add (or ask your ' - 'reviewer to add) the "$_missingVersionChangeOverrideLabel" ' - 'label.'); + 'not be verified to be exempt\n' + 'from version changes according to repository policy.\n' + 'If this is a false positive, please comment in ' + 'the PR to explain why the PR\n' + 'is exempt, and add (or ask your reviewer to add) the ' + '"$_missingVersionChangeOverrideLabel" label.'); return 'Missing version change'; } } @@ -572,13 +573,13 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. logWarning('Ignoring lack of CHANGELOG update due to the ' '"$_missingChangelogChangeOverrideLabel" label.'); } else { - printError( - 'No CHANGELOG change found. If this PR needs an exemption from ' - 'the standard policy of listing all changes in the CHANGELOG, ' + printError('No CHANGELOG change found.\n' + 'If this PR needs an exemption from the standard policy of listing ' + 'all changes in the CHANGELOG,\n' 'comment in the PR to explain why the PR is exempt, and add (or ' - 'ask your reviewer to add) the ' - '"$_missingChangelogChangeOverrideLabel" label. Otherwise, ' - 'please add a NEXT entry in the CHANGELOG as described in ' + 'ask your reviewer to add) the\n' + '"$_missingChangelogChangeOverrideLabel" label.\n' + 'Otherwise, please add a NEXT entry in the CHANGELOG as described in ' 'the contributing guide.'); return 'Missing CHANGELOG change'; } diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart index db519c008233..c603453db0c2 100644 --- a/script/tool/test/common/repository_package_test.dart +++ b/script/tool/test/common/repository_package_test.dart @@ -102,6 +102,7 @@ void main() { final List examples = plugin.getExamples().toList(); expect(examples.length, 1); + expect(examples[0].isExample, isTrue); expect(examples[0].path, getExampleDir(plugin).path); }); @@ -112,6 +113,8 @@ void main() { final List examples = plugin.getExamples().toList(); expect(examples.length, 2); + expect(examples[0].isExample, isTrue); + expect(examples[1].isExample, isTrue); expect(examples[0].path, getExampleDir(plugin).childDirectory('example1').path); expect(examples[1].path, @@ -125,6 +128,7 @@ void main() { final List examples = package.getExamples().toList(); expect(examples.length, 1); + expect(examples[0].isExample, isTrue); expect(examples[0].path, getExampleDir(package).path); }); @@ -136,6 +140,8 @@ void main() { final List examples = package.getExamples().toList(); expect(examples.length, 2); + expect(examples[0].isExample, isTrue); + expect(examples[1].isExample, isTrue); expect(examples[0].path, getExampleDir(package).childDirectory('example1').path); expect(examples[1].path, @@ -151,6 +157,7 @@ void main() { expect(plugin.isAppFacing, false); expect(plugin.isPlatformInterface, false); expect(plugin.isFederated, false); + expect(plugin.isExample, isFalse); }); test('handle app-facing packages', () { @@ -160,6 +167,7 @@ void main() { expect(plugin.isAppFacing, true); expect(plugin.isPlatformInterface, false); expect(plugin.isPlatformImplementation, false); + expect(plugin.isExample, isFalse); }); test('handle platform interface packages', () { @@ -170,6 +178,7 @@ void main() { expect(plugin.isAppFacing, false); expect(plugin.isPlatformInterface, true); expect(plugin.isPlatformImplementation, false); + expect(plugin.isExample, isFalse); }); test('handle platform implementation packages', () { @@ -181,6 +190,7 @@ void main() { expect(plugin.isAppFacing, false); expect(plugin.isPlatformInterface, false); expect(plugin.isPlatformImplementation, true); + expect(plugin.isExample, isFalse); }); }); diff --git a/script/tool/test/update_dependency_command_test.dart b/script/tool/test/update_dependency_command_test.dart index 0fd3969210f6..deee0d629b87 100644 --- a/script/tool/test/update_dependency_command_test.dart +++ b/script/tool/test/update_dependency_command_test.dart @@ -690,7 +690,7 @@ How is it even possible that I didn't specify a Gradle distribution? output, containsAllInOrder([ contains( - 'Unable to find a "distributionUrl" entry to update for ${package.displayName}.'), + 'Unable to find a gradle version entry to update for ${package.displayName}/example.'), ]), ); }); @@ -779,30 +779,28 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip r'distributionUrl=https\://services.gradle.org/distributions/' 'gradle-$newGradleVersion-all.zip')); }); - }); - }); - test('succeeds if one example app runs on Android and another does not', - () async { - final RepositoryPackage package = createFakePlugin( - 'fake_plugin', packagesDir, examples: [ - 'example_1', - 'example_2' - ], extraFiles: [ - 'example/example_2/android/app/gradle/wrapper/gradle-wrapper.properties' - ]); - const String newGradleVersion = '8.8.8'; - - final File gradleWrapperPropertiesFile = package.directory - .childDirectory('example') - .childDirectory('example_2') - .childDirectory('android') - .childDirectory('app') - .childDirectory('gradle') - .childDirectory('wrapper') - .childFile('gradle-wrapper.properties'); - - gradleWrapperPropertiesFile.writeAsStringSync(r''' + test('succeeds if one example app runs on Android and another does not', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, examples: [ + 'example_1', + 'example_2' + ], extraFiles: [ + 'example/example_2/android/app/gradle/wrapper/gradle-wrapper.properties' + ]); + const String newGradleVersion = '8.8.8'; + + final File gradleWrapperPropertiesFile = package.directory + .childDirectory('example') + .childDirectory('example_2') + .childDirectory('android') + .childDirectory('app') + .childDirectory('gradle') + .childDirectory('wrapper') + .childFile('gradle-wrapper.properties'); + + gradleWrapperPropertiesFile.writeAsStringSync(r''' distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME @@ -810,21 +808,315 @@ zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip '''); - await runCapturingPrint(runner, [ - 'update-dependency', - '--packages', - package.displayName, - '--android-dependency', - 'gradle', - '--version', - newGradleVersion, - ]); + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'gradle', + '--version', + newGradleVersion, + ]); - final String updatedGradleWrapperPropertiesContents = - gradleWrapperPropertiesFile.readAsStringSync(); - expect( - updatedGradleWrapperPropertiesContents, - contains(r'distributionUrl=https\://services.gradle.org/distributions/' - 'gradle-$newGradleVersion-all.zip')); + final String updatedGradleWrapperPropertiesContents = + gradleWrapperPropertiesFile.readAsStringSync(); + expect( + updatedGradleWrapperPropertiesContents, + contains( + r'distributionUrl=https\://services.gradle.org/distributions/' + 'gradle-$newGradleVersion-all.zip')); + }); + }); + + group('compileSdk/compileSdkForExamples', () { + // Tests if the compileSdk version is updated for the provided + // build.gradle file and new compileSdk version to update to. + Future testCompileSdkVersionUpdated( + {required RepositoryPackage package, + required File buildGradleFile, + required String oldCompileSdkVersion, + required String newCompileSdkVersion, + bool runForExamples = false, + bool checkForDeprecatedCompileSdkVersion = false}) async { + buildGradleFile.writeAsStringSync(''' +android { + // Conditional for compatibility with AGP <4.2. + if (project.android.hasProperty("namespace")) { + namespace 'io.flutter.plugins.pathprovider' + } + ${checkForDeprecatedCompileSdkVersion ? 'compileSdkVersion' : 'compileSdk'} $oldCompileSdkVersion +'''); + + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + if (runForExamples) 'compileSdkForExamples' else 'compileSdk', + '--version', + newCompileSdkVersion, + ]); + + final String updatedBuildGradleContents = + buildGradleFile.readAsStringSync(); + // compileSdkVersion is now deprecated, so if the tool finds any + // instances of compileSdk OR compileSdkVersion, it should change it + // to compileSdk. See https://developer.android.com/reference/tools/gradle-api/7.2/com/android/build/api/dsl/CommonExtension#compileSdkVersion(kotlin.Int). + expect(updatedBuildGradleContents, + contains('compileSdk $newCompileSdkVersion')); + } + + test('throws if version format is invalid for compileSdk', () async { + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--android-dependency', + 'compileSdk', + '--version', + '834', + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains( + 'A valid Android SDK version number (1-2 digit numbers) must be provided.'), + ]), + ); + }); + + test('throws if version format is invalid for compileSdkForExamples', + () async { + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--android-dependency', + 'compileSdkForExamples', + '--version', + '438', + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains( + 'A valid Android SDK version number (1-2 digit numbers) must be provided.'), + ]), + ); + }); + + test('skips if plugin does not run on Android', () async { + final RepositoryPackage package = + createFakePlugin('fake_plugin', packagesDir); + + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'compileSdk', + '--version', + '34', + ]); + + expect( + output, + containsAllInOrder([ + contains( + 'SKIPPING: Package ${package.displayName} does not run on Android.'), + ]), + ); + }); + + test('skips if plugin example does not run on Android', () async { + final RepositoryPackage package = + createFakePlugin('fake_plugin', packagesDir); + + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'compileSdkForExamples', + '--version', + '34', + ]); + + expect( + output, + containsAllInOrder([ + contains('SKIPPING: No example apps run on Android.'), + ]), + ); + }); + + test( + 'throws if build configuration file does not have compileSdk version with expected format for compileSdk', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, + extraFiles: ['android/build.gradle']); + + final File buildGradleFile = package.directory + .childDirectory('android') + .childFile('build.gradle'); + + buildGradleFile.writeAsStringSync(''' +How is it even possible that I didn't specify a compileSdk version? +'''); + + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'compileSdk', + '--version', + '34', + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains( + 'Unable to find a compileSdk version entry to update for ${package.displayName}.'), + ]), + ); + }); + + test( + 'throws if build configuration file does not have compileSdk version with expected format for compileSdkForExamples', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, + extraFiles: ['example/android/app/build.gradle']); + + final File buildGradleFile = package.directory + .childDirectory('example') + .childDirectory('android') + .childDirectory('app') + .childFile('build.gradle'); + + buildGradleFile.writeAsStringSync(''' +How is it even possible that I didn't specify a compileSdk version? +'''); + + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'compileSdkForExamples', + '--version', + '34', + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains( + 'Unable to find a compileSdkForExamples version entry to update for ${package.displayName}/example.'), + ]), + ); + }); + + test( + 'succeeds if plugin runs on Android and valid version is supplied for compileSdkVersion entry', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, extraFiles: [ + 'android/build.gradle', + 'example/android/app/build.gradle' + ]); + final File buildGradleFile = package.directory + .childDirectory('android') + .childFile('build.gradle'); + + await testCompileSdkVersionUpdated( + package: package, + buildGradleFile: buildGradleFile, + oldCompileSdkVersion: '8', + newCompileSdkVersion: '16', + checkForDeprecatedCompileSdkVersion: true); + }); + + test( + 'succeeds if plugin example runs on Android and valid version is supplied for compileSdkVersion entry', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, extraFiles: [ + 'android/build.gradle', + 'example/android/app/build.gradle' + ]); + final File exampleBuildGradleFile = package.directory + .childDirectory('example') + .childDirectory('android') + .childDirectory('app') + .childFile('build.gradle'); + + await testCompileSdkVersionUpdated( + package: package, + buildGradleFile: exampleBuildGradleFile, + oldCompileSdkVersion: '8', + newCompileSdkVersion: '16', + runForExamples: true, + checkForDeprecatedCompileSdkVersion: true); + }); + + test( + 'succeeds if plugin runs on Android and valid version is supplied for compileSdk entry', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, extraFiles: [ + 'android/build.gradle', + 'example/android/app/build.gradle' + ]); + + final File buildGradleFile = package.directory + .childDirectory('android') + .childFile('build.gradle'); + await testCompileSdkVersionUpdated( + package: package, + buildGradleFile: buildGradleFile, + oldCompileSdkVersion: '8', + newCompileSdkVersion: '16'); + }); + + test( + 'succeeds if plugin example runs on Android and valid version is supplied for compileSdk entry', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, extraFiles: [ + 'android/build.gradle', + 'example/android/app/build.gradle' + ]); + + final File exampleBuildGradleFile = package.directory + .childDirectory('example') + .childDirectory('android') + .childDirectory('app') + .childFile('build.gradle'); + await testCompileSdkVersionUpdated( + package: package, + buildGradleFile: exampleBuildGradleFile, + oldCompileSdkVersion: '33', + newCompileSdkVersion: '34', + runForExamples: true); + }); + }); }); } diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart index da864de409a9..13bee57785c0 100644 --- a/script/tool/test/version_check_command_test.dart +++ b/script/tool/test/version_check_command_test.dart @@ -961,7 +961,7 @@ packages/plugin/example/lib/foo.dart expect( output, containsAllInOrder([ - contains('No CHANGELOG change found'), + contains('No CHANGELOG change found.\nIf'), contains('plugin:\n' ' Missing CHANGELOG change'), ]), @@ -1222,7 +1222,10 @@ packages/plugin/lib/plugin.dart expect( output, containsAllInOrder([ - contains('No version change found'), + contains( + 'No version change found, but the change to this package could ' + 'not be verified to be exempt\n', + ), contains('plugin:\n' ' Missing version change'), ]),